diff --git a/docs/assets/auditoria-visual-2026-09-14/comparacion-albumes-canciones.jpg b/docs/assets/auditoria-visual-2026-09-14/comparacion-albumes-canciones.jpg new file mode 100644 index 0000000..1ea7801 Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/comparacion-albumes-canciones.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/estudio-canciones-medidas.json b/docs/assets/auditoria-visual-2026-09-14/estudio-canciones-medidas.json new file mode 100644 index 0000000..6252d56 --- /dev/null +++ b/docs/assets/auditoria-visual-2026-09-14/estudio-canciones-medidas.json @@ -0,0 +1,2326 @@ +[ + { + "background": "rgba(0, 0, 0, 0)", + "clase": "marca svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 108, + "line": "36px", + "padding": "24px 24px 16px", + "size": "24px", + "tag": "A", + "text": "Senza PauraAdministración", + "weight": "600", + "width": 249 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Administración", + "weight": "500", + "width": 127.109375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "inicio svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "⌂ Inicio", + "weight": "400", + "width": 217 + }, + { + "background": "oklch(0.26 0.05 264)", + "clase": "svelte-w5uecg activo", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Canciones", + "weight": "600", + "width": 217 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Lanzamientos", + "weight": "400", + "width": 217 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 61.1875, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Personas e identidades", + "weight": "400", + "width": 217 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Estilos del catálogo", + "weight": "400", + "width": 217 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Etiquetas", + "weight": "400", + "width": 217 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Blog", + "weight": "400", + "width": 217 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Páginas", + "weight": "400", + "width": 217 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Textos legales", + "weight": "400", + "width": 217 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Capítulos", + "weight": "400", + "width": 217 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Géneros musicales", + "weight": "400", + "width": 217 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Glosario", + "weight": "400", + "width": 217 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Prompts", + "weight": "400", + "width": 217 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Pedidos", + "weight": "400", + "width": 217 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Cuentas", + "weight": "400", + "width": 217 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Suscripciones", + "weight": "400", + "width": 217 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 61.1875, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Medios y procesamiento", + "weight": "400", + "width": 217 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Modelos de IA", + "weight": "400", + "width": 217 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Vocabularios", + "weight": "400", + "width": 217 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Redirecciones", + "weight": "400", + "width": 217 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Actividad", + "weight": "400", + "width": 217 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "oculto svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 1, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "LABEL", + "text": "Buscar en Estudio", + "weight": "600", + "width": 1 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 48, + "line": "24px", + "padding": "12px 16px", + "size": "16px", + "tag": "INPUT", + "text": "Buscar en Estudio…", + "weight": "400", + "width": 220 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 52, + "line": "36px", + "padding": "8px", + "size": "24px", + "tag": "BUTTON", + "text": "Buscar", + "weight": "400", + "width": 29.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "ver-sitio svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Ver sitio ↗", + "weight": "400", + "width": 71.046875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 48, + "line": "24px", + "padding": "12px", + "size": "16px", + "tag": "A", + "text": "Lanzamiento", + "weight": "400", + "width": 156 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 72, + "line": "24px", + "padding": "12px", + "size": "16px", + "tag": "A", + "text": "Persona o identidad", + "weight": "400", + "width": 156 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 48, + "line": "24px", + "padding": "12px", + "size": "16px", + "tag": "A", + "text": "Entrada", + "weight": "400", + "width": 156 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "boton pequeno svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 42, + "line": "24px", + "padding": "8px 12px", + "size": "16px", + "tag": "BUTTON", + "text": "Cerrar sesión", + "weight": "600", + "width": 124.75 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.96 0.012 255)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 51, + "line": "51px", + "padding": "0px", + "size": "34px", + "tag": "H1", + "text": "Canciones", + "weight": "600", + "width": 354.75 + }, + { + "background": "oklch(0.8 0.145 84)", + "clase": "boton primario", + "color": "oklch(0.16 0.042 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 50, + "line": "24px", + "padding": "12px 16px", + "size": "16px", + "tag": "A", + "text": "+ Crear canción", + "weight": "600", + "width": 155.609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "oculto", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 1, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "LABEL", + "text": "Buscar canciones", + "weight": "600", + "width": 1 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 50, + "line": "24px", + "padding": "12px 16px", + "size": "16px", + "tag": "INPUT", + "text": "Buscar canciones…", + "weight": "400", + "width": 329.265625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "oculto", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 1, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "LABEL", + "text": "Estado", + "weight": "600", + "width": 1 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 45, + "line": "normal", + "padding": "12px 16px", + "size": "16px", + "tag": "SELECT", + "text": "Todos los estadosBorradorPublicadoRetirado", + "weight": "400", + "width": 183 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "oculto", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 1, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "LABEL", + "text": "Relacionado con", + "weight": "600", + "width": 1 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 45, + "line": "normal", + "padding": "12px 16px", + "size": "16px", + "tag": "SELECT", + "text": "Cualquier relaciónAgustín LaraAlfredo Le PeraAndrés BermejoArsenio RodríguezArti", + "weight": "400", + "width": 220 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "boton", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 50, + "line": "24px", + "padding": "12px 16px", + "size": "16px", + "tag": "BUTTON", + "text": "Buscar", + "weight": "600", + "width": 86.734375 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 56.5, + "line": "24px", + "padding": "16px 24px", + "size": "16px", + "tag": "TH", + "text": "Canción", + "weight": "600", + "width": 328.09375 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 56.5, + "line": "24px", + "padding": "16px 24px", + "size": "16px", + "tag": "TH", + "text": "Estado", + "weight": "600", + "width": 151 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 56.5, + "line": "24px", + "padding": "16px 24px", + "size": "16px", + "tag": "TH", + "text": "Preparación", + "weight": "600", + "width": 173.65625 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 56.5, + "line": "24px", + "padding": "16px 24px", + "size": "16px", + "tag": "TH", + "text": "Última edición", + "weight": "600", + "width": 187.203125 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 56.5, + "line": "24px", + "padding": "16px 24px", + "size": "16px", + "tag": "TH", + "text": "Abrir ficha", + "weight": "600", + "width": 63.046875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Canción 2e6ccade", + "weight": "600", + "width": 136.9375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "cancion-2e6ccade", + "weight": "400", + "width": 132.21875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Canción 2e6ccade", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Canción de prueba 2b67411f", + "weight": "600", + "width": 211.15625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "cancion-de-prueba-2b67411f", + "weight": "400", + "width": 206.65625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Canción de prueba 2b67411f", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Canción de prueba 298b9b66", + "weight": "600", + "width": 215.671875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "cancion-de-prueba-298b9b66", + "weight": "400", + "width": 213.546875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Canción de prueba 298b9b66", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Canción de prueba c6ced9a6", + "weight": "600", + "width": 213.9375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "cancion-de-prueba-c6ced9a6", + "weight": "400", + "width": 210.5 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Canción de prueba c6ced9a6", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Canción c0b72737", + "weight": "600", + "width": 138.1875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "cancion-c0b72737", + "weight": "400", + "width": 132.734375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Canción c0b72737", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Canción con letra e2696519", + "weight": "600", + "width": 204.75 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "cancion-con-letra-e2696519", + "weight": "400", + "width": 201.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Canción con letra e2696519", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Canción de prueba f1f2cdb3", + "weight": "600", + "width": 206.625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "cancion-de-prueba-f1f2cdb3", + "weight": "400", + "width": 201.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Canción de prueba f1f2cdb3", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Canción de prueba 47c4541c", + "weight": "600", + "width": 214.296875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "cancion-de-prueba-47c4541c", + "weight": "400", + "width": 210.046875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Canción de prueba 47c4541c", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Canción a7fbae8a", + "weight": "600", + "width": 133.15625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "cancion-a7fbae8a", + "weight": "400", + "width": 127.90625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Canción a7fbae8a", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Ámame esta noche, nada más", + "weight": "600", + "width": 219.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "amame-esta-noche-nada-mas", + "weight": "400", + "width": 215.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Ámame esta noche, nada más", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Canción de prueba d7489bd5", + "weight": "600", + "width": 215.9375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "cancion-de-prueba-d7489bd5", + "weight": "400", + "width": 212.265625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Canción de prueba d7489bd5", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Canción de prueba c1b31631", + "weight": "600", + "width": 214.984375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "cancion-de-prueba-c1b31631", + "weight": "400", + "width": 209.71875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Canción de prueba c1b31631", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Prometiste", + "weight": "600", + "width": 80.390625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "prometiste", + "weight": "400", + "width": 75.90625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Prometiste", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Canción con letra f6228ece", + "weight": "600", + "width": 199.78125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "cancion-con-letra-f6228ece", + "weight": "400", + "width": 196.296875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Canción con letra f6228ece", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Cuando tus manos me tocan", + "weight": "600", + "width": 208.53125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "cuando-tus-manos-me-tocan", + "weight": "400", + "width": 207.640625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Cuando tus manos me tocan", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Canción con letra b0c9b294", + "weight": "600", + "width": 205.046875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "cancion-con-letra-b0c9b294", + "weight": "400", + "width": 201.390625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Canción con letra b0c9b294", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Canción de prueba 2caa47fd", + "weight": "600", + "width": 209.96875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "cancion-de-prueba-2caa47fd", + "weight": "400", + "width": 206.484375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Canción de prueba 2caa47fd", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Dorina", + "weight": "600", + "width": 49.28125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "dorina", + "weight": "400", + "width": 44.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Dorina", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Canción de prueba d53fa3ad", + "weight": "600", + "width": 210.65625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "cancion-de-prueba-d53fa3ad", + "weight": "400", + "width": 207.28125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Canción de prueba d53fa3ad", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Canción de prueba 454021a2", + "weight": "600", + "width": 214.875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "cancion-de-prueba-454021a2", + "weight": "400", + "width": 211.734375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Canción de prueba 454021a2", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Quédate", + "weight": "600", + "width": 63.1875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "quedate", + "weight": "400", + "width": 58.15625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Quédate", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Canción de prueba 0ee2d980", + "weight": "600", + "width": 214.921875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "cancion-de-prueba-0ee2d980", + "weight": "400", + "width": 212.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Canción de prueba 0ee2d980", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Inventario a los 56", + "weight": "600", + "width": 134.421875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "inventario-a-los-56", + "weight": "400", + "width": 134.421875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Inventario a los 56", + "weight": "400", + "width": 13.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Canción de prueba 4cc3b4d5", + "weight": "600", + "width": 214.796875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "cancion-de-prueba-4cc3b4d5", + "weight": "400", + "width": 211.28125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Completo", + "weight": "400", + "width": 69.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "12/9/2026", + "weight": "400", + "width": 72.46875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Abrir Canción de prueba 4cc3b4d5", + "weight": "400", + "width": 13.8125 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "boton pequeno", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 42, + "line": "24px", + "padding": "8px 12px", + "size": "16px", + "tag": "A", + "text": "Siguiente", + "weight": "600", + "width": 95.84375 + } +] \ No newline at end of file diff --git a/docs/assets/auditoria-visual-2026-09-14/estudio-canciones-oscuro.jpg b/docs/assets/auditoria-visual-2026-09-14/estudio-canciones-oscuro.jpg new file mode 100644 index 0000000..cd0006a Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/estudio-canciones-oscuro.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/estudio-dialogo-publicacion.jpg b/docs/assets/auditoria-visual-2026-09-14/estudio-dialogo-publicacion.jpg new file mode 100644 index 0000000..ac23cbb Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/estudio-dialogo-publicacion.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/estudio-editor-oscuro.jpg b/docs/assets/auditoria-visual-2026-09-14/estudio-editor-oscuro.jpg new file mode 100644 index 0000000..1b62bf7 Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/estudio-editor-oscuro.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/estudio-ficha-medidas.json b/docs/assets/auditoria-visual-2026-09-14/estudio-ficha-medidas.json new file mode 100644 index 0000000..00f3224 --- /dev/null +++ b/docs/assets/auditoria-visual-2026-09-14/estudio-ficha-medidas.json @@ -0,0 +1,982 @@ +[ + { + "background": "rgba(0, 0, 0, 0)", + "clase": "marca svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 108, + "line": "36px", + "padding": "24px 24px 16px", + "size": "24px", + "tag": "A", + "text": "Senza PauraAdministración", + "weight": "600", + "width": 264 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Administración", + "weight": "500", + "width": 127.109375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "inicio svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "⌂ Inicio", + "weight": "400", + "width": 232 + }, + { + "background": "oklch(0.26 0.05 264)", + "clase": "svelte-w5uecg activo", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Canciones", + "weight": "600", + "width": 232 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Lanzamientos", + "weight": "400", + "width": 232 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Personas e identidades", + "weight": "400", + "width": 232 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Estilos del catálogo", + "weight": "400", + "width": 232 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Etiquetas", + "weight": "400", + "width": 232 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Blog", + "weight": "400", + "width": 232 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Páginas", + "weight": "400", + "width": 232 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Textos legales", + "weight": "400", + "width": 232 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Capítulos", + "weight": "400", + "width": 232 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Géneros musicales", + "weight": "400", + "width": 232 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Glosario", + "weight": "400", + "width": 232 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Prompts", + "weight": "400", + "width": 232 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Pedidos", + "weight": "400", + "width": 232 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Cuentas", + "weight": "400", + "width": 232 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Suscripciones", + "weight": "400", + "width": 232 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Medios y procesamiento", + "weight": "400", + "width": 232 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Modelos de IA", + "weight": "400", + "width": 232 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Vocabularios", + "weight": "400", + "width": 232 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Redirecciones", + "weight": "400", + "width": 232 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 34.59375, + "line": "26.6px", + "padding": "4px 12px", + "size": "19px", + "tag": "A", + "text": "Actividad", + "weight": "400", + "width": 232 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "oculto svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 1, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "LABEL", + "text": "Buscar en Estudio", + "weight": "600", + "width": 1 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 48, + "line": "24px", + "padding": "12px 16px", + "size": "16px", + "tag": "INPUT", + "text": "Buscar en Estudio…", + "weight": "400", + "width": 220 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 52, + "line": "36px", + "padding": "8px", + "size": "24px", + "tag": "BUTTON", + "text": "Buscar", + "weight": "400", + "width": 29.84375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "ver-sitio svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Ver sitio ↗", + "weight": "400", + "width": 71.046875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 48, + "line": "24px", + "padding": "12px", + "size": "16px", + "tag": "A", + "text": "Canción", + "weight": "400", + "width": 156 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 48, + "line": "24px", + "padding": "12px", + "size": "16px", + "tag": "A", + "text": "Lanzamiento", + "weight": "400", + "width": 156 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 72, + "line": "24px", + "padding": "12px", + "size": "16px", + "tag": "A", + "text": "Persona o identidad", + "weight": "400", + "width": 156 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 48, + "line": "24px", + "padding": "12px", + "size": "16px", + "tag": "A", + "text": "Entrada", + "weight": "400", + "width": 156 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-w5uecg", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "estudio@example.test", + "weight": "400", + "width": 159.9375 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "boton pequeno svelte-w5uecg", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 42, + "line": "24px", + "padding": "8px 12px", + "size": "16px", + "tag": "BUTTON", + "text": "Cerrar sesión", + "weight": "600", + "width": 124.75 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-53yoji", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Canciones", + "weight": "400", + "width": 76.34375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-53yoji", + "color": "oklch(0.96 0.012 255)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 51, + "line": "51px", + "padding": "0px", + "size": "34px", + "tag": "H1", + "text": "Canción 2e6ccade", + "weight": "600", + "width": 240.84375 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "boton svelte-53yoji", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 50, + "line": "24px", + "padding": "12px 16px", + "size": "16px", + "tag": "BUTTON", + "text": "Guardar borrador", + "weight": "600", + "width": 161.796875 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "boton svelte-53yoji", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 50, + "line": "24px", + "padding": "12px 16px", + "size": "16px", + "tag": "A", + "text": "Vista previa ↗", + "weight": "600", + "width": 134.9375 + }, + { + "background": "oklch(0.8 0.145 84)", + "clase": "boton primario svelte-53yoji", + "color": "oklch(0.16 0.042 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 50, + "line": "24px", + "padding": "12px 16px", + "size": "16px", + "tag": "BUTTON", + "text": "Revisar y publicar", + "weight": "600", + "width": 164.453125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-53yoji", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 58, + "line": "24px", + "padding": "16px 4px", + "size": "16px", + "tag": "A", + "text": "Datos y contenido", + "weight": "600", + "width": 140.75 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-53yoji", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 58, + "line": "24px", + "padding": "16px 4px", + "size": "16px", + "tag": "A", + "text": "Grabaciones", + "weight": "400", + "width": 99.375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-53yoji", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 58, + "line": "24px", + "padding": "16px 4px", + "size": "16px", + "tag": "A", + "text": "Visibilidad y acceso", + "weight": "400", + "width": 146.59375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-53yoji", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 58, + "line": "24px", + "padding": "16px 4px", + "size": "16px", + "tag": "A", + "text": "Resumen y usos", + "weight": "400", + "width": 126.125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-53yoji", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 58, + "line": "24px", + "padding": "16px 4px", + "size": "16px", + "tag": "A", + "text": "Actividad", + "weight": "400", + "width": 74.109375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-53yoji", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "36px", + "padding": "0px", + "size": "24px", + "tag": "H2", + "text": "Datos de la ficha", + "weight": "700", + "width": 551 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "ancho svelte-53yoji", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 82, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "LABEL", + "text": "Título", + "weight": "600", + "width": 551 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "svelte-53yoji", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 50, + "line": "24px", + "padding": "12px 16px", + "size": "16px", + "tag": "INPUT", + "text": "", + "weight": "600", + "width": 551 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-ti6721", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 189.953125, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "LABEL", + "text": "Descripción breve", + "weight": "600", + "width": 551 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-ti6721", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 157, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "LABEL", + "text": "Plantilla Detalle de canción Composición visual controlada para este tipo de con", + "weight": "600", + "width": 263.5 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "svelte-ti6721", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 53, + "line": "normal", + "padding": "12px 16px", + "size": "16px", + "tag": "SELECT", + "text": "Detalle de canción", + "weight": "600", + "width": 263.5 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-ti6721", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 56, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Composición visual controlada para este tipo de contenido.", + "weight": "400", + "width": 263.5 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-ti6721", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 157, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "LABEL", + "text": "Hero AutomáticoVisibleOculto Automático usa una cabecera amplia solo cuando hay ", + "weight": "600", + "width": 263.5 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "svelte-ti6721", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 45, + "line": "normal", + "padding": "12px 16px", + "size": "16px", + "tag": "SELECT", + "text": "AutomáticoVisibleOculto", + "weight": "600", + "width": 263.5 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-ti6721", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 72, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Automático usa una cabecera amplia solo cuando hay contenido suficiente.", + "weight": "400", + "width": 263.5 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "svelte-p8k2p9", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "24px", + "padding": "8px 12px", + "size": "16px", + "tag": "BUTTON", + "text": "+ Texto", + "weight": "600", + "width": 86.0625 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "svelte-p8k2p9", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "24px", + "padding": "8px 12px", + "size": "16px", + "tag": "BUTTON", + "text": "+ Código", + "weight": "600", + "width": 98.84375 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "svelte-p8k2p9", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "24px", + "padding": "8px 12px", + "size": "16px", + "tag": "BUTTON", + "text": "+ Vídeo", + "weight": "600", + "width": 87.984375 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "svelte-p8k2p9", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "24px", + "padding": "8px 12px", + "size": "16px", + "tag": "BUTTON", + "text": "+ Imagen", + "weight": "600", + "width": 100.171875 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "svelte-p8k2p9", + "color": "oklch(0.8 0.145 84)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "24px", + "padding": "8px 12px", + "size": "16px", + "tag": "BUTTON", + "text": "+ Sonido", + "weight": "600", + "width": 97.859375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-ti6721", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 109, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "LABEL", + "text": "Grabación principal Seleccionar…Canción 2e6ccadeMedios 06b1475cOtro audio 07be4b", + "weight": "600", + "width": 551 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "svelte-ti6721", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 45, + "line": "normal", + "padding": "12px 16px", + "size": "16px", + "tag": "SELECT", + "text": "Seleccionar…Canción 2e6ccadeMedios 06b1475cOtro audio 07be4b04Versión f9cf6de8", + "weight": "600", + "width": 551 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-ti6721", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "La interpretación que presenta esta canción en la web.", + "weight": "400", + "width": 551 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "ancho svelte-53yoji", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 138, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "LABEL", + "text": "Dirección en la webSe conserva al cambiar el título. Modificarla requiere manten", + "weight": "600", + "width": 551 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "svelte-53yoji", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 50, + "line": "24px", + "padding": "12px 16px", + "size": "16px", + "tag": "INPUT", + "text": "", + "weight": "600", + "width": 551 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-53yoji", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 48, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Se conserva al cambiar el título. Modificarla requiere mantener los enlaces\n\t\t\t\t", + "weight": "400", + "width": 551 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-53yoji", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "36px", + "padding": "0px", + "size": "24px", + "tag": "H2", + "text": "Antes de publicar", + "weight": "700", + "width": 230 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-53yoji", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 72, + "line": "36px", + "padding": "0px", + "size": "24px", + "tag": "H2", + "text": "Guardado y publicación", + "weight": "700", + "width": 230 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-53yoji", + "color": "oklch(0.74 0.022 258)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 41, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "SMALL", + "text": "Último guardado: 12/9/2026, 18:06:47", + "weight": "400", + "width": 202.59375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "check svelte-53yoji", + "color": "oklch(0.96 0.012 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "LABEL", + "text": "Confirmo la retirada", + "weight": "600", + "width": 230 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-53yoji", + "color": "rgb(255, 255, 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "24px", + "padding": "0px", + "size": "16px", + "tag": "INPUT", + "text": "", + "weight": "600", + "width": 17 + }, + { + "background": "oklch(0.21 0.046 264)", + "clase": "boton peligro separado svelte-53yoji", + "color": "oklch(0.72 0.16 22)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 50, + "line": "24px", + "padding": "12px 16px", + "size": "16px", + "tag": "BUTTON", + "text": "Retirar publicación", + "weight": "600", + "width": 172.234375 + } +] \ No newline at end of file diff --git a/docs/assets/auditoria-visual-2026-09-14/estudio-ficha-movil.jpg b/docs/assets/auditoria-visual-2026-09-14/estudio-ficha-movil.jpg new file mode 100644 index 0000000..ae66ba1 Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/estudio-ficha-movil.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/estudio-ficha-oscuro.jpg b/docs/assets/auditoria-visual-2026-09-14/estudio-ficha-oscuro.jpg new file mode 100644 index 0000000..8f34632 Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/estudio-ficha-oscuro.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/estudio-inicio-oscuro.jpg b/docs/assets/auditoria-visual-2026-09-14/estudio-inicio-oscuro.jpg new file mode 100644 index 0000000..7d354ab Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/estudio-inicio-oscuro.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/estudio-medios.jpg b/docs/assets/auditoria-visual-2026-09-14/estudio-medios.jpg new file mode 100644 index 0000000..d17ccec Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/estudio-medios.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/estudio-pedidos.jpg b/docs/assets/auditoria-visual-2026-09-14/estudio-pedidos.jpg new file mode 100644 index 0000000..fa3b132 Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/estudio-pedidos.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/web-album-claro.jpg b/docs/assets/auditoria-visual-2026-09-14/web-album-claro.jpg new file mode 100644 index 0000000..80926d0 Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/web-album-claro.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/web-blog-claro.jpg b/docs/assets/auditoria-visual-2026-09-14/web-blog-claro.jpg new file mode 100644 index 0000000..b414686 Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/web-blog-claro.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/web-blog-medidas.json b/docs/assets/auditoria-visual-2026-09-14/web-blog-medidas.json new file mode 100644 index 0000000..34c21b5 --- /dev/null +++ b/docs/assets/auditoria-visual-2026-09-14/web-blog-medidas.json @@ -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 + } +] \ No newline at end of file diff --git a/docs/assets/auditoria-visual-2026-09-14/web-cancion-claro.jpg b/docs/assets/auditoria-visual-2026-09-14/web-cancion-claro.jpg new file mode 100644 index 0000000..5bf4e80 Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/web-cancion-claro.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/web-cancion-medidas.json b/docs/assets/auditoria-visual-2026-09-14/web-cancion-medidas.json new file mode 100644 index 0000000..cae518b --- /dev/null +++ b/docs/assets/auditoria-visual-2026-09-14/web-cancion-medidas.json @@ -0,0 +1,1318 @@ +[ + { + "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-1jtdvn7", + "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", + "size": "14px", + "tag": "A", + "text": "Música", + "weight": "400", + "width": 44.921875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1jtdvn7", + "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", + "size": "14px", + "tag": "A", + "text": "Baladas románticas", + "weight": "400", + "width": 123.203125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1jtdvn7", + "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", + "size": "14px", + "tag": "A", + "text": "Cupido sin flechas", + "weight": "400", + "width": 113.765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-12taq32", + "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": "Ámame esta noche, nada más", + "weight": "400", + "width": 768 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-12taq32", + "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": "Cupido sin flechas", + "weight": "400", + "width": 113.765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl", + "color": "oklch(0.45 0.13 320)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 28, + "line": "16.2px", + "padding": "4px 12px", + "size": "12px", + "tag": "A", + "text": "Deseo", + "weight": "400", + "width": 60.921875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl", + "color": "oklch(0.45 0.13 165)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 28, + "line": "16.2px", + "padding": "4px 12px", + "size": "12px", + "tag": "A", + "text": "Una noche", + "weight": "400", + "width": 83.734375 + }, + { + "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": "Autoengaño", + "weight": "400", + "width": 91.421875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "estrellas estrellas-lectura svelte-1csjfik", + "color": "oklch(0.86 0.02 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 28, + "line": "28px", + "padding": "0px", + "size": "28px", + "tag": "A", + "text": "Inicia sesión para valorar esta canción", + "weight": "400", + "width": 132.640625 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "15px", + "padding": "12px 24px", + "size": "15px", + "tag": "BUTTON", + "text": "Escuchar completa", + "weight": "400", + "width": 204.21875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 50.75, + "line": "24.75px", + "padding": "12px 24px", + "size": "15px", + "tag": "BUTTON", + "text": "Añadir al carrito", + "weight": "400", + "width": 153.53125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 52 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "entrar svelte-1ed5omq", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "26.4px", + "padding": "8px 12px", + "size": "16px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a una lista de reproducción", + "weight": "400", + "width": 52 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "pestana svelte-k0es79", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "18px", + "padding": "8px 12px", + "size": "15px", + "tag": "BUTTON", + "text": "Letra", + "weight": "600", + "width": 59.296875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "pestana svelte-k0es79", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "18px", + "padding": "8px 12px", + "size": "15px", + "tag": "BUTTON", + "text": "Versiones", + "weight": "400", + "width": 90.140625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "visually-hidden svelte-k0es79", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 1, + "line": "37.4px", + "padding": "0px", + "size": "34px", + "tag": "H2", + "text": "Letra", + "weight": "400", + "width": 1 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-10qjjkj", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "18px", + "padding": "8px 12px", + "size": "15px", + "tag": "H2", + "text": "Ficha", + "weight": "600", + "width": 254 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-10qjjkj", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 16, + "line": "24.75px", + "padding": "0px", + "size": "15px", + "tag": "A", + "text": "Cupido sin flechas", + "weight": "400", + "width": 121.890625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-10qjjkj", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 16, + "line": "24.75px", + "padding": "0px", + "size": "15px", + "tag": "A", + "text": "Baladas románticas", + "weight": "400", + "width": 132 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-10qjjkj", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "18px", + "padding": "8px 12px", + "size": "15px", + "tag": "H2", + "text": "Créditos", + "weight": "600", + "width": 254 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1ll61x4", + "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": "Canciones relacionadas", + "weight": "400", + "width": 355.671875 + }, + { + "background": "oklch(0.955 0.011 264)", + "clase": "svelte-1ll61x4", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "31.35px", + "padding": "1px 6px", + "size": "19px", + "tag": "BUTTON", + "text": "Ver canciones anteriores", + "weight": "400", + "width": 44 + }, + { + "background": "oklch(0.955 0.011 264)", + "clase": "svelte-1ll61x4", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "31.35px", + "padding": "1px 6px", + "size": "19px", + "tag": "BUTTON", + "text": "Ver más canciones", + "weight": "400", + "width": 44 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 41.154541015625, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "H3", + "text": "Cuando tus manos me tocan", + "weight": "400", + "width": 202.91000366210935 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 39.292236328125, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "A", + "text": "Cuando tus manos me tocan", + "weight": "400", + "width": 118.44625091552734 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 35.4599609375, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir Cuando tus manos me tocan", + "weight": "400", + "width": 35.459991455078125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "0px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 43.339996337890625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "entrar svelte-1ed5omq", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a una lista de reproducción", + "weight": "400", + "width": 43.339988708496094 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 62.932281494140625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 41.154541015625, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "H3", + "text": "Pagué por una noche de amor", + "weight": "400", + "width": 202.91000366210935 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 39.292236328125, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "A", + "text": "Pagué por una noche de amor", + "weight": "400", + "width": 108.24227905273438 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 35.4599609375, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir Pagué por una noche de amor", + "weight": "400", + "width": 35.4599609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "0px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 43.339996337890625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "entrar svelte-1ed5omq", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a una lista de reproducción", + "weight": "400", + "width": 43.339996337890625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 62.9322509765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 20.5771484375, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "H3", + "text": "Quédate", + "weight": "400", + "width": 62.36279296875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 18.715087890625, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "A", + "text": "Quédate", + "weight": "400", + "width": 62.36279296875 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 35.4599609375, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir Quédate", + "weight": "400", + "width": 35.4599609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "0px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 43.3399658203125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "entrar svelte-1ed5omq", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a una lista de reproducción", + "weight": "400", + "width": 43.34002685546875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 62.9322509765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 20.5771484375, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "H3", + "text": "Inventario a los 56", + "weight": "400", + "width": 135.6375732421875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 18.715087890625, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "A", + "text": "Inventario a los 56", + "weight": "400", + "width": 135.6375732421875 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 35.4599609375, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir Inventario a los 56", + "weight": "400", + "width": 35.4599609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "0px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 43.3399658203125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "entrar svelte-1ed5omq", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a una lista de reproducción", + "weight": "400", + "width": 43.3399658203125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 62.9322509765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 20.5771484375, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "H3", + "text": "No me mientas", + "weight": "400", + "width": 110.181396484375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 18.715087890625, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "A", + "text": "No me mientas", + "weight": "400", + "width": 110.1815185546875 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 35.4599609375, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir No me mientas", + "weight": "400", + "width": 35.4599609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "0px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 43.3399658203125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "entrar svelte-1ed5omq", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a una lista de reproducción", + "weight": "400", + "width": 43.3399658203125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 62.9322509765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 20.5771484375, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "H3", + "text": "Para qué recordar", + "weight": "400", + "width": 128.6810302734375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 18.715087890625, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "A", + "text": "Para qué recordar", + "weight": "400", + "width": 128.6810302734375 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 35.4599609375, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir Para qué recordar", + "weight": "400", + "width": 35.4599609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "0px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 43.3399658203125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "entrar svelte-1ed5omq", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a una lista de reproducción", + "weight": "400", + "width": 43.3399658203125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 62.9322509765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 20.5771484375, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "H3", + "text": "Prometiste", + "weight": "400", + "width": 79.4464111328125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 18.715087890625, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "A", + "text": "Prometiste", + "weight": "400", + "width": 79.4464111328125 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 35.4599609375, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir Prometiste", + "weight": "400", + "width": 35.4599609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "0px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 43.3399658203125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "entrar svelte-1ed5omq", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a una lista de reproducción", + "weight": "400", + "width": 43.3399658203125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 62.93212890625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 20.5771484375, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "H3", + "text": "Ton silence me libère", + "weight": "400", + "width": 153.244384765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 18.715087890625, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "A", + "text": "Ton silence me libère", + "weight": "400", + "width": 153.244384765625 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 35.4599609375, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir Ton silence me libère", + "weight": "400", + "width": 35.4599609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "0px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 43.340087890625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "entrar svelte-1ed5omq", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a una lista de reproducción", + "weight": "400", + "width": 43.33984375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 62.93212890625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "vecino derecha svelte-12taq32", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 46.1875, + "line": "23.1px", + "padding": "0px", + "size": "14px", + "tag": "A", + "text": "Siguiente Cuando tus manos me tocan", + "weight": "400", + "width": 568 + }, + { + "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 + } +] \ No newline at end of file diff --git a/docs/assets/auditoria-visual-2026-09-14/web-contacto-claro.jpg b/docs/assets/auditoria-visual-2026-09-14/web-contacto-claro.jpg new file mode 100644 index 0000000..d4ac866 Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/web-contacto-claro.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/web-contacto-medidas.json b/docs/assets/auditoria-visual-2026-09-14/web-contacto-medidas.json new file mode 100644 index 0000000..64db1ff --- /dev/null +++ b/docs/assets/auditoria-visual-2026-09-14/web-contacto-medidas.json @@ -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 + } +] \ No newline at end of file diff --git a/docs/assets/auditoria-visual-2026-09-14/web-interpretes-claro.jpg b/docs/assets/auditoria-visual-2026-09-14/web-interpretes-claro.jpg new file mode 100644 index 0000000..d45cc20 Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/web-interpretes-claro.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/web-jukebox-claro.jpg b/docs/assets/auditoria-visual-2026-09-14/web-jukebox-claro.jpg new file mode 100644 index 0000000..b826322 Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/web-jukebox-claro.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/web-portada-claro.jpg b/docs/assets/auditoria-visual-2026-09-14/web-portada-claro.jpg new file mode 100644 index 0000000..54859a6 Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/web-portada-claro.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/web-suscripciones-claro.jpg b/docs/assets/auditoria-visual-2026-09-14/web-suscripciones-claro.jpg new file mode 100644 index 0000000..b3ddea2 Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/web-suscripciones-claro.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/web-tablas-claro.jpg b/docs/assets/auditoria-visual-2026-09-14/web-tablas-claro.jpg new file mode 100644 index 0000000..174b2e9 Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/web-tablas-claro.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/web-tablas-medidas.json b/docs/assets/auditoria-visual-2026-09-14/web-tablas-medidas.json new file mode 100644 index 0000000..0cfd18a --- /dev/null +++ b/docs/assets/auditoria-visual-2026-09-14/web-tablas-medidas.json @@ -0,0 +1,2648 @@ +[ + { + "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": "oklch(0.915 0.016 264)", + "clase": "boton svelte-10d17h5", + "color": "oklch(0.24 0.055 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-bkp4k9", + "color": "oklch(0.52 0.125 74)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Todo el catálogo", + "weight": "600", + "width": 218 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Todos los álbumes", + "weight": "400", + "width": 218 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Todas las canciones", + "weight": "400", + "width": 218 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Baladas románticas 2", + "weight": "400", + "width": 218 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Clásica 0", + "weight": "400", + "width": 218 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Rock 0", + "weight": "400", + "width": 218 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Pop melódico 0", + "weight": "400", + "width": 218 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Español 10", + "weight": "400", + "width": 109 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Portugués 0", + "weight": "400", + "width": 109 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Inglés 0", + "weight": "400", + "width": 109 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Francés 1", + "weight": "400", + "width": 109 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Italiano 0", + "weight": "400", + "width": 109 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Armenio 0", + "weight": "400", + "width": 109 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Búlgaro 0", + "weight": "400", + "width": 109 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Latín 0", + "weight": "400", + "width": 109 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Irlandés 0", + "weight": "400", + "width": 109 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1bssaz3", + "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": "Música", + "weight": "400", + "width": 864 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1bssaz3", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Conocer a los intérpretes →", + "weight": "400", + "width": 193.859375 + }, + { + "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 en el catálogo", + "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, estilo, idioma, disco, año o etiqueta", + "weight": "400", + "width": 562.5625 + }, + { + "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": "Tarjetas", + "weight": "400", + "width": 100.890625 + }, + { + "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": "Tabla", + "weight": "400", + "width": 85.125 + }, + { + "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": "Etiquetas", + "weight": "400", + "width": 112.421875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1bssaz3", + "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": "Álbumes", + "weight": "400", + "width": 121.5 + }, + { + "background": "oklch(0.98 0.008 264)", + "clase": "control svelte-enohi9", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44.796875, + "line": "23.1px", + "padding": "0px 4px", + "size": "14px", + "tag": "TH", + "text": "Reproducir", + "weight": "500", + "width": 52 + }, + { + "background": "oklch(0.98 0.008 264)", + "clase": "control svelte-enohi9", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44.796875, + "line": "23.1px", + "padding": "0px 4px", + "size": "14px", + "tag": "TH", + "text": "Portada", + "weight": "500", + "width": 88 + }, + { + "background": "oklch(0.98 0.008 264)", + "clase": "columna-titulo svelte-enohi9", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44.796875, + "line": "23.1px", + "padding": "0px", + "size": "14px", + "tag": "TH", + "text": "Título", + "weight": "500", + "width": 342.359375 + }, + { + "background": "oklch(0.98 0.008 264)", + "clase": "numerica svelte-enohi9", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44.796875, + "line": "23.1px", + "padding": "0px", + "size": "14px", + "tag": "TH", + "text": "Año", + "weight": "500", + "width": 88.28125 + }, + { + "background": "oklch(0.98 0.008 264)", + "clase": "numerica svelte-enohi9", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44.796875, + "line": "23.1px", + "padding": "0px", + "size": "14px", + "tag": "TH", + "text": "Temas", + "weight": "500", + "width": 121.90625 + }, + { + "background": "oklch(0.98 0.008 264)", + "clase": "numerica svelte-enohi9", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44.796875, + "line": "23.1px", + "padding": "0px", + "size": "14px", + "tag": "TH", + "text": "Duración", + "weight": "500", + "width": 171.453125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-itx5fr", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "14px", + "padding": "8px", + "size": "14px", + "tag": "BUTTON", + "text": "Reproducir todo", + "weight": "400", + "width": 44 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "mini svelte-enohi9", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 80, + "line": "0px", + "padding": "0px", + "size": "14px", + "tag": "A", + "text": "", + "weight": "400", + "width": 80 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "detalle columna-titulo svelte-enohi9", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 97, + "line": "23.1px", + "padding": "8px 4px", + "size": "14px", + "tag": "TH", + "text": "Cupido sin flechas Álbum · Baladas románticas 2026 · 10\n\t\t\t\t\t\ttemas · 43:41", + "weight": "400", + "width": 342.359375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-enohi9", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Cupido sin flechas", + "weight": "400", + "width": 130.03125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-enohi9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 14, + "line": "15px", + "padding": "0px", + "size": "12px", + "tag": "A", + "text": "Baladas románticas", + "weight": "400", + "width": 105.609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-itx5fr", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "14px", + "padding": "8px", + "size": "14px", + "tag": "BUTTON", + "text": "Reproducir todo", + "weight": "400", + "width": 44 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "mini svelte-enohi9", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 80, + "line": "0px", + "padding": "0px", + "size": "14px", + "tag": "A", + "text": "", + "weight": "400", + "width": 80 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "detalle columna-titulo svelte-enohi9", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 96, + "line": "23.1px", + "padding": "8px 4px", + "size": "14px", + "tag": "TH", + "text": "Singles Colección · Baladas románticas 2026 · 1\n\t\t\t\t\t\ttema · 4:38", + "weight": "400", + "width": 342.359375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-enohi9", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Singles", + "weight": "400", + "width": 52.8125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-enohi9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 14, + "line": "15px", + "padding": "0px", + "size": "12px", + "tag": "A", + "text": "Baladas románticas", + "weight": "400", + "width": 105.609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1bssaz3", + "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": "Canciones", + "weight": "400", + "width": 142.53125 + }, + { + "background": "oklch(0.98 0.008 264)", + "clase": "control svelte-1dmhvd", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44.796875, + "line": "23.1px", + "padding": "0px 4px", + "size": "14px", + "tag": "TH", + "text": "Reproducir", + "weight": "500", + "width": 88 + }, + { + "background": "oklch(0.98 0.008 264)", + "clase": "columna-titulo svelte-1dmhvd", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44.796875, + "line": "23.1px", + "padding": "0px", + "size": "14px", + "tag": "TH", + "text": "Título ↑", + "weight": "500", + "width": 278.953125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1dmhvd", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.796875, + "line": "19.8px", + "padding": "12px 8px", + "size": "12px", + "tag": "BUTTON", + "text": "Título ↑", + "weight": "500", + "width": 278.953125 + }, + { + "background": "oklch(0.98 0.008 264)", + "clase": "columna-album svelte-1dmhvd", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44.796875, + "line": "23.1px", + "padding": "0px", + "size": "14px", + "tag": "TH", + "text": "Álbum", + "weight": "400", + "width": 140.515625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1dmhvd", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.796875, + "line": "19.8px", + "padding": "12px 8px", + "size": "12px", + "tag": "BUTTON", + "text": "Álbum", + "weight": "500", + "width": 140.515625 + }, + { + "background": "oklch(0.98 0.008 264)", + "clase": "columna-ano svelte-1dmhvd numerica", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44.796875, + "line": "23.1px", + "padding": "0px", + "size": "14px", + "tag": "TH", + "text": "Año", + "weight": "500", + "width": 69.609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1dmhvd", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.796875, + "line": "19.8px", + "padding": "12px 8px", + "size": "12px", + "tag": "BUTTON", + "text": "Año", + "weight": "500", + "width": 69.609375 + }, + { + "background": "oklch(0.98 0.008 264)", + "clase": "numerica svelte-1dmhvd", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44.796875, + "line": "23.1px", + "padding": "0px", + "size": "14px", + "tag": "TH", + "text": "Duración", + "weight": "500", + "width": 119.03125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1dmhvd", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.796875, + "line": "19.8px", + "padding": "12px 8px", + "size": "12px", + "tag": "BUTTON", + "text": "Duración", + "weight": "500", + "width": 119.03125 + }, + { + "background": "oklch(0.98 0.008 264)", + "clase": "control svelte-1dmhvd", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44.796875, + "line": "23.1px", + "padding": "0px 4px", + "size": "14px", + "tag": "TH", + "text": "Favorito", + "weight": "500", + "width": 48 + }, + { + "background": "oklch(0.98 0.008 264)", + "clase": "control svelte-1dmhvd", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44.796875, + "line": "23.1px", + "padding": "0px 4px", + "size": "14px", + "tag": "TH", + "text": "Cola", + "weight": "500", + "width": 48 + }, + { + "background": "oklch(0.98 0.008 264)", + "clase": "control svelte-1dmhvd", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44.796875, + "line": "23.1px", + "padding": "0px 4px", + "size": "14px", + "tag": "TH", + "text": "Comprar", + "weight": "500", + "width": 71.890625 + }, + { + "background": "oklab(0.153859 -0.00874737 -0.0352487 / 0.45)", + "clase": "boton svelte-itx5fr", + "color": "oklch(0.97 0.01 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 80, + "line": "14px", + "padding": "8px", + "size": "14px", + "tag": "BUTTON", + "text": "Reproducir Ámame esta noche, nada más", + "weight": "400", + "width": 80 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "columna-titulo svelte-1dmhvd", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 97, + "line": "23.1px", + "padding": "8px 4px", + "size": "14px", + "tag": "TH", + "text": "Ámame esta noche, nada más Estilo: Baladas románticas Deseo Una noche Autoenga", + "weight": "400", + "width": 278.953125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-15gwb2v", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 26.390625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Ámame esta noche, nada más", + "weight": "400", + "width": 214.5 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-15gwb2v", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 15, + "line": "15px", + "padding": "0px", + "size": "12px", + "tag": "A", + "text": "Baladas románticas", + "weight": "400", + "width": 105.609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 320)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Deseo", + "weight": "400", + "width": 52.921875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 165)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Una noche", + "weight": "400", + "width": 75.734375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 290)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Autoengaño", + "weight": "400", + "width": 83.421875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1dmhvd", + "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": "Cupido sin flechas", + "weight": "400", + "width": 113.765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1h1zi5 compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir «Ámame esta noche, nada más» a la cola", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 63.890625 + }, + { + "background": "oklab(0.153859 -0.00874737 -0.0352487 / 0.45)", + "clase": "boton svelte-itx5fr", + "color": "oklch(0.97 0.01 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 80, + "line": "14px", + "padding": "8px", + "size": "14px", + "tag": "BUTTON", + "text": "Reproducir Cuando tus manos me tocan", + "weight": "400", + "width": 80 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "columna-titulo svelte-1dmhvd", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 97, + "line": "23.1px", + "padding": "8px 4px", + "size": "14px", + "tag": "TH", + "text": "Cuando tus manos me tocan Estilo: Baladas románticas Deseo Entrega", + "weight": "400", + "width": 278.953125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-15gwb2v", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 26.390625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Cuando tus manos me tocan", + "weight": "400", + "width": 203.046875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-15gwb2v", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 15, + "line": "15px", + "padding": "0px", + "size": "12px", + "tag": "A", + "text": "Baladas románticas", + "weight": "400", + "width": 105.609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 320)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Deseo", + "weight": "400", + "width": 52.921875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 290)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Entrega", + "weight": "400", + "width": 60.109375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1dmhvd", + "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": "Cupido sin flechas", + "weight": "400", + "width": 113.765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1h1zi5 compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir «Cuando tus manos me tocan» a la cola", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 63.890625 + }, + { + "background": "oklab(0.153859 -0.00874737 -0.0352487 / 0.45)", + "clase": "boton svelte-itx5fr", + "color": "oklch(0.97 0.01 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 80, + "line": "14px", + "padding": "8px", + "size": "14px", + "tag": "BUTTON", + "text": "Reproducir Dorina", + "weight": "400", + "width": 80 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "columna-titulo svelte-1dmhvd", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 97, + "line": "23.1px", + "padding": "8px 4px", + "size": "14px", + "tag": "TH", + "text": "Dorina Estilo: Baladas románticas", + "weight": "400", + "width": 278.953125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-15gwb2v", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 26.390625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Dorina", + "weight": "400", + "width": 47.515625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-15gwb2v", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 15, + "line": "15px", + "padding": "0px", + "size": "12px", + "tag": "A", + "text": "Baladas románticas", + "weight": "400", + "width": 105.609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1dmhvd", + "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": "Singles", + "weight": "400", + "width": 46.203125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1h1zi5 compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir «Dorina» a la cola", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 63.890625 + }, + { + "background": "oklab(0.153859 -0.00874737 -0.0352487 / 0.45)", + "clase": "boton svelte-itx5fr", + "color": "oklch(0.97 0.01 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 80, + "line": "14px", + "padding": "8px", + "size": "14px", + "tag": "BUTTON", + "text": "Reproducir Inventario a los 56", + "weight": "400", + "width": 80 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "columna-titulo svelte-1dmhvd", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 97, + "line": "23.1px", + "padding": "8px 4px", + "size": "14px", + "tag": "TH", + "text": "Inventario a los 56 Estilo: Baladas románticas Paso del tiempo Balance", + "weight": "400", + "width": 278.953125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-15gwb2v", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 26.390625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Inventario a los 56", + "weight": "400", + "width": 128.5 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-15gwb2v", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 15, + "line": "15px", + "padding": "0px", + "size": "12px", + "tag": "A", + "text": "Baladas románticas", + "weight": "400", + "width": 105.609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 20)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Paso del tiempo", + "weight": "400", + "width": 103.265625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 290)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Balance", + "weight": "400", + "width": 61.71875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1dmhvd", + "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": "Cupido sin flechas", + "weight": "400", + "width": 113.765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1h1zi5 compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir «Inventario a los 56» a la cola", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 63.890625 + }, + { + "background": "oklab(0.153859 -0.00874737 -0.0352487 / 0.45)", + "clase": "boton svelte-itx5fr", + "color": "oklch(0.97 0.01 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 80, + "line": "14px", + "padding": "8px", + "size": "14px", + "tag": "BUTTON", + "text": "Reproducir No me mientas", + "weight": "400", + "width": 80 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "columna-titulo svelte-1dmhvd", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 97, + "line": "23.1px", + "padding": "8px 4px", + "size": "14px", + "tag": "TH", + "text": "No me mientas Estilo: Baladas románticas Mentiras Celos Ruptura", + "weight": "400", + "width": 278.953125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-15gwb2v", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 26.390625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "No me mientas", + "weight": "400", + "width": 106.890625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-15gwb2v", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 15, + "line": "15px", + "padding": "0px", + "size": "12px", + "tag": "A", + "text": "Baladas románticas", + "weight": "400", + "width": 105.609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 135)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Mentiras", + "weight": "400", + "width": 64.296875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 45)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Celos", + "weight": "400", + "width": 48.984375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 200)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Ruptura", + "weight": "400", + "width": 60.875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1dmhvd", + "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": "Cupido sin flechas", + "weight": "400", + "width": 113.765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1h1zi5 compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir «No me mientas» a la cola", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 63.890625 + }, + { + "background": "oklab(0.153859 -0.00874737 -0.0352487 / 0.45)", + "clase": "boton svelte-itx5fr", + "color": "oklch(0.97 0.01 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 80, + "line": "14px", + "padding": "8px", + "size": "14px", + "tag": "BUTTON", + "text": "Reproducir Pagué por una noche de amor", + "weight": "400", + "width": 80 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "columna-titulo svelte-1dmhvd", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 97, + "line": "23.1px", + "padding": "8px 4px", + "size": "14px", + "tag": "TH", + "text": "Pagué por una noche de amor Estilo: Baladas románticas Una noche Soledad Compa", + "weight": "400", + "width": 278.953125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-15gwb2v", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 26.390625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Pagué por una noche de amor", + "weight": "400", + "width": 211.453125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-15gwb2v", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 15, + "line": "15px", + "padding": "0px", + "size": "12px", + "tag": "A", + "text": "Baladas románticas", + "weight": "400", + "width": 105.609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 165)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Una noche", + "weight": "400", + "width": 75.734375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 165)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Soledad", + "weight": "400", + "width": 62.34375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 135)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Compasión", + "weight": "400", + "width": 79.65625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1dmhvd", + "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": "Cupido sin flechas", + "weight": "400", + "width": 113.765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1h1zi5 compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir «Pagué por una noche de amor» a la cola", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 63.890625 + }, + { + "background": "oklab(0.153859 -0.00874737 -0.0352487 / 0.45)", + "clase": "boton svelte-itx5fr", + "color": "oklch(0.97 0.01 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 80, + "line": "14px", + "padding": "8px", + "size": "14px", + "tag": "BUTTON", + "text": "Reproducir Para qué recordar", + "weight": "400", + "width": 80 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "columna-titulo svelte-1dmhvd", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 97, + "line": "23.1px", + "padding": "8px 4px", + "size": "14px", + "tag": "TH", + "text": "Para qué recordar Estilo: Baladas románticas Melancolía Recuerdos", + "weight": "400", + "width": 278.953125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-15gwb2v", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 26.390625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Para qué recordar", + "weight": "400", + "width": 126.203125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-15gwb2v", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 15, + "line": "15px", + "padding": "0px", + "size": "12px", + "tag": "A", + "text": "Baladas románticas", + "weight": "400", + "width": 105.609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 45)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Melancolía", + "weight": "400", + "width": 75.75 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 20)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Recuerdos", + "weight": "400", + "width": 76.359375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1dmhvd", + "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": "Cupido sin flechas", + "weight": "400", + "width": 113.765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1h1zi5 compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir «Para qué recordar» a la cola", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 63.890625 + }, + { + "background": "oklab(0.153859 -0.00874737 -0.0352487 / 0.45)", + "clase": "boton svelte-itx5fr", + "color": "oklch(0.97 0.01 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 80, + "line": "14px", + "padding": "8px", + "size": "14px", + "tag": "BUTTON", + "text": "Reproducir Prometiste", + "weight": "400", + "width": 80 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "columna-titulo svelte-1dmhvd", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 97, + "line": "23.1px", + "padding": "8px 4px", + "size": "14px", + "tag": "TH", + "text": "Prometiste Estilo: Baladas románticas Promesas Desamor", + "weight": "400", + "width": 278.953125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-15gwb2v", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 26.390625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Prometiste", + "weight": "400", + "width": 77.484375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-15gwb2v", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 15, + "line": "15px", + "padding": "0px", + "size": "12px", + "tag": "A", + "text": "Baladas románticas", + "weight": "400", + "width": 105.609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 200)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Promesas", + "weight": "400", + "width": 72.375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 20)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Desamor", + "weight": "400", + "width": 67.203125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1dmhvd", + "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": "Cupido sin flechas", + "weight": "400", + "width": 113.765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1h1zi5 compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir «Prometiste» a la cola", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 63.890625 + }, + { + "background": "oklab(0.153859 -0.00874737 -0.0352487 / 0.45)", + "clase": "boton svelte-itx5fr", + "color": "oklch(0.97 0.01 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 80, + "line": "14px", + "padding": "8px", + "size": "14px", + "tag": "BUTTON", + "text": "Reproducir Quédate", + "weight": "400", + "width": 80 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "columna-titulo svelte-1dmhvd", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 97, + "line": "23.1px", + "padding": "8px 4px", + "size": "14px", + "tag": "TH", + "text": "Quédate Estilo: Baladas románticas Deseo Despedidas Súplica", + "weight": "400", + "width": 278.953125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-15gwb2v", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 26.390625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Quédate", + "weight": "400", + "width": 61.6875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-15gwb2v", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 15, + "line": "15px", + "padding": "0px", + "size": "12px", + "tag": "A", + "text": "Baladas románticas", + "weight": "400", + "width": 105.609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 320)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Deseo", + "weight": "400", + "width": 52.921875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 230)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Despedidas", + "weight": "400", + "width": 81.859375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 320)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Súplica", + "weight": "400", + "width": 57.796875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1dmhvd", + "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": "Cupido sin flechas", + "weight": "400", + "width": 113.765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1h1zi5 compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir «Quédate» a la cola", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 63.890625 + }, + { + "background": "oklab(0.153859 -0.00874737 -0.0352487 / 0.45)", + "clase": "boton svelte-itx5fr", + "color": "oklch(0.97 0.01 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 80, + "line": "14px", + "padding": "8px", + "size": "14px", + "tag": "BUTTON", + "text": "Reproducir Ton silence me libère", + "weight": "400", + "width": 80 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "columna-titulo svelte-1dmhvd", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 97, + "line": "23.1px", + "padding": "8px 4px", + "size": "14px", + "tag": "TH", + "text": "Ton silence me libère Estilo: Baladas románticas Silencio Rabia Ruptura", + "weight": "400", + "width": 278.953125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-15gwb2v", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 26.390625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Ton silence me libère", + "weight": "400", + "width": 148.28125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-15gwb2v", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 15, + "line": "15px", + "padding": "0px", + "size": "12px", + "tag": "A", + "text": "Baladas románticas", + "weight": "400", + "width": 105.609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 350)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Silencio", + "weight": "400", + "width": 60.578125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 230)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Rabia", + "weight": "400", + "width": 49.203125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 200)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Ruptura", + "weight": "400", + "width": 60.875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1dmhvd", + "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": "Cupido sin flechas", + "weight": "400", + "width": 113.765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1h1zi5 compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir «Ton silence me libère» a la cola", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 63.890625 + }, + { + "background": "oklab(0.153859 -0.00874737 -0.0352487 / 0.45)", + "clase": "boton svelte-itx5fr", + "color": "oklch(0.97 0.01 255)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 80, + "line": "14px", + "padding": "8px", + "size": "14px", + "tag": "BUTTON", + "text": "Reproducir Tus malas maneras", + "weight": "400", + "width": 80 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "columna-titulo svelte-1dmhvd", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 96, + "line": "23.1px", + "padding": "8px 4px", + "size": "14px", + "tag": "TH", + "text": "Tus malas maneras Estilo: Baladas románticas Rabia Despecho Mentiras", + "weight": "400", + "width": 278.953125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-15gwb2v", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 26.390625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Tus malas maneras", + "weight": "400", + "width": 137.671875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-15gwb2v", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 15, + "line": "15px", + "padding": "0px", + "size": "12px", + "tag": "A", + "text": "Baladas románticas", + "weight": "400", + "width": 105.609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 230)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Rabia", + "weight": "400", + "width": 49.203125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 320)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Despecho", + "weight": "400", + "width": 72.71875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "etiqueta svelte-q0tptl menuda", + "color": "oklch(0.45 0.13 135)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 24, + "line": "14.4px", + "padding": "1px 8px", + "size": "12px", + "tag": "A", + "text": "Mentiras", + "weight": "400", + "width": 64.296875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1dmhvd", + "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": "Cupido sin flechas", + "weight": "400", + "width": 113.765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1h1zi5 compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir «Tus malas maneras» a la cola", + "weight": "400", + "width": 40 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 44, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 63.890625 + }, + { + "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 + } +] \ No newline at end of file diff --git a/docs/assets/auditoria-visual-2026-09-14/web-tablas-movil.jpg b/docs/assets/auditoria-visual-2026-09-14/web-tablas-movil.jpg new file mode 100644 index 0000000..f51a9dc Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/web-tablas-movil.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/web-tablas-oscuro.jpg b/docs/assets/auditoria-visual-2026-09-14/web-tablas-oscuro.jpg new file mode 100644 index 0000000..fb3f06d Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/web-tablas-oscuro.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/web-taller-claro.jpg b/docs/assets/auditoria-visual-2026-09-14/web-taller-claro.jpg new file mode 100644 index 0000000..a5adfeb Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/web-taller-claro.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/web-tarjetas-claro.jpg b/docs/assets/auditoria-visual-2026-09-14/web-tarjetas-claro.jpg new file mode 100644 index 0000000..bc9005c Binary files /dev/null and b/docs/assets/auditoria-visual-2026-09-14/web-tarjetas-claro.jpg differ diff --git a/docs/assets/auditoria-visual-2026-09-14/web-tarjetas-medidas.json b/docs/assets/auditoria-visual-2026-09-14/web-tarjetas-medidas.json new file mode 100644 index 0000000..1e4a70f --- /dev/null +++ b/docs/assets/auditoria-visual-2026-09-14/web-tarjetas-medidas.json @@ -0,0 +1,1668 @@ +[ + { + "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-bkp4k9", + "color": "oklch(0.52 0.125 74)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Todo el catálogo", + "weight": "600", + "width": 218 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Todos los álbumes", + "weight": "400", + "width": 218 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Todas las canciones", + "weight": "400", + "width": 218 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Baladas románticas 2", + "weight": "400", + "width": 218 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Clásica 0", + "weight": "400", + "width": 218 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Rock 0", + "weight": "400", + "width": 218 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Pop melódico 0", + "weight": "400", + "width": 218 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Español 10", + "weight": "400", + "width": 109 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Portugués 0", + "weight": "400", + "width": 109 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Inglés 0", + "weight": "400", + "width": 109 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Francés 1", + "weight": "400", + "width": 109 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Italiano 0", + "weight": "400", + "width": 109 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Armenio 0", + "weight": "400", + "width": 109 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Búlgaro 0", + "weight": "400", + "width": 109 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Latín 0", + "weight": "400", + "width": 109 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-bkp4k9", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "23.1px", + "padding": "4px 0px 4px 12px", + "size": "14px", + "tag": "A", + "text": "Irlandés 0", + "weight": "400", + "width": 109 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1bssaz3", + "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": "Música", + "weight": "400", + "width": 864 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1bssaz3", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 17, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Conocer a los intérpretes →", + "weight": "400", + "width": 193.859375 + }, + { + "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 en el catálogo", + "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, estilo, idioma, disco, año o etiqueta", + "weight": "400", + "width": 562.5625 + }, + { + "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": "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": "Etiquetas", + "weight": "400", + "width": 112.421875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1bssaz3", + "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": "Álbumes", + "weight": "400", + "width": 121.5 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir todo", + "weight": "400", + "width": 36 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-cmp8nk", + "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": "H3", + "text": "Cupido sin flechas", + "weight": "400", + "width": 238 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-cmp8nk", + "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": "Cupido sin flechas", + "weight": "400", + "width": 173.484375 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 36, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir todo", + "weight": "400", + "width": 36 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-cmp8nk", + "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": "H3", + "text": "Singles", + "weight": "400", + "width": 238 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-cmp8nk", + "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": "Singles", + "weight": "400", + "width": 67.09375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "svelte-1bssaz3", + "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": "Canciones", + "weight": "400", + "width": 142.53125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 41.154541015625, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "H3", + "text": "Ámame esta noche, nada más", + "weight": "400", + "width": 187.1499938964844 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 39.292236328125, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "A", + "text": "Ámame esta noche, nada más", + "weight": "400", + "width": 119.8468017578125 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 35.4600830078125, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir Ámame esta noche, nada más", + "weight": "400", + "width": 35.4599609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "0px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 43.339996337890625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "entrar svelte-1ed5omq", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a una lista de reproducción", + "weight": "400", + "width": 43.339996337890625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 62.9322509765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 41.154541015625, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "H3", + "text": "Cuando tus manos me tocan", + "weight": "400", + "width": 187.1500244140625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 39.292236328125, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "A", + "text": "Cuando tus manos me tocan", + "weight": "400", + "width": 118.44622802734376 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 35.4600830078125, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir Cuando tus manos me tocan", + "weight": "400", + "width": 35.4599609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "0px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 43.34002685546875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "entrar svelte-1ed5omq", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a una lista de reproducción", + "weight": "400", + "width": 43.3399658203125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 62.9322509765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 20.5772705078125, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "H3", + "text": "Inventario a los 56", + "weight": "400", + "width": 135.63763427734375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 18.7149658203125, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "A", + "text": "Inventario a los 56", + "weight": "400", + "width": 135.63763427734375 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 35.4599609375, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir Inventario a los 56", + "weight": "400", + "width": 35.4599609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "0px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 43.34002685546875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "entrar svelte-1ed5omq", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a una lista de reproducción", + "weight": "400", + "width": 43.34002685546875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 62.9322509765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 20.577392578125, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "H3", + "text": "No me mientas", + "weight": "400", + "width": 110.18148803710938 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 18.715087890625, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "A", + "text": "No me mientas", + "weight": "400", + "width": 110.18148803710938 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 35.4599609375, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir No me mientas", + "weight": "400", + "width": 35.4599609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.33984375, + "line": "21.45px", + "padding": "0px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 43.339996337890625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "entrar svelte-1ed5omq", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.33984375, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a una lista de reproducción", + "weight": "400", + "width": 43.339996337890625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.33984375, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 62.9322509765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 41.154541015625, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "H3", + "text": "Pagué por una noche de amor", + "weight": "400", + "width": 187.1500244140625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 39.292236328125, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "A", + "text": "Pagué por una noche de amor", + "weight": "400", + "width": 108.24224853515624 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 35.4599609375, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir Pagué por una noche de amor", + "weight": "400", + "width": 35.4599609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.33984375, + "line": "21.45px", + "padding": "0px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 43.34002685546875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "entrar svelte-1ed5omq", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.33984375, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a una lista de reproducción", + "weight": "400", + "width": 43.3399658203125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.33984375, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 62.9322509765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 20.577392578125, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "H3", + "text": "Para qué recordar", + "weight": "400", + "width": 128.68109130859375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 18.715087890625, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "A", + "text": "Para qué recordar", + "weight": "400", + "width": 128.68096923828125 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 35.4599609375, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir Para qué recordar", + "weight": "400", + "width": 35.4599609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.33984375, + "line": "21.45px", + "padding": "0px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 43.34002685546875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "entrar svelte-1ed5omq", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.33984375, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a una lista de reproducción", + "weight": "400", + "width": 43.34002685546875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.33984375, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 62.9322509765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 20.5771484375, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "H3", + "text": "Prometiste", + "weight": "400", + "width": 79.44638061523438 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 18.715087890625, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "A", + "text": "Prometiste", + "weight": "400", + "width": 79.4464111328125 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 35.460205078125, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir Prometiste", + "weight": "400", + "width": 35.4599609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "0px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 43.339996337890625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "entrar svelte-1ed5omq", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a una lista de reproducción", + "weight": "400", + "width": 43.339996337890625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 62.9322509765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 20.5771484375, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "H3", + "text": "Quédate", + "weight": "400", + "width": 62.36285400390625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 18.715087890625, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "A", + "text": "Quédate", + "weight": "400", + "width": 62.36279296875 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 35.460205078125, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir Quédate", + "weight": "400", + "width": 35.4599609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "0px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 43.34002685546875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "entrar svelte-1ed5omq", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a una lista de reproducción", + "weight": "400", + "width": 43.3399658203125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 62.9322509765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 20.5771484375, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "H3", + "text": "Ton silence me libère", + "weight": "400", + "width": 153.24444580078125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 18.715087890625, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "A", + "text": "Ton silence me libère", + "weight": "400", + "width": 153.24444580078125 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 35.460205078125, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir Ton silence me libère", + "weight": "400", + "width": 35.4599609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "0px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 43.34002685546875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "entrar svelte-1ed5omq", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a una lista de reproducción", + "weight": "400", + "width": 43.34002685546875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 62.9322509765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 20.5771484375, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "H3", + "text": "Tus malas maneras", + "weight": "400", + "width": 136.20700073242188 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 18.715087890625, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "A", + "text": "Tus malas maneras", + "weight": "400", + "width": 136.20703125 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 35.460205078125, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir Tus malas maneras", + "weight": "400", + "width": 35.4599609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "0px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 43.339996337890625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "entrar svelte-1ed5omq", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a una lista de reproducción", + "weight": "400", + "width": 43.339996337890625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 62.9322509765625 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "titulo svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 20.5771484375, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "H3", + "text": "Dorina", + "weight": "400", + "width": 52.1434326171875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "tarjeta__enlace svelte-117p2aw", + "color": "oklch(0.24 0.055 264)", + "font": "Newsreader, \"Iowan Old Style\", Georgia, serif", + "height": 18.715087890625, + "line": "20.9px", + "padding": "0px", + "size": "19px", + "tag": "A", + "text": "Dorina", + "weight": "400", + "width": 52.1434326171875 + }, + { + "background": "oklch(0.76 0.13 82)", + "clase": "boton svelte-itx5fr solido", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 35.460205078125, + "line": "15px", + "padding": "0px", + "size": "15px", + "tag": "BUTTON", + "text": "Reproducir Dorina", + "weight": "400", + "width": 35.4599609375 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-16g9k1a compacto", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "0px", + "size": "13px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a favoritos", + "weight": "400", + "width": 43.34002685546875 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "entrar svelte-1ed5omq", + "color": "oklch(0.48 0.038 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "26.4px", + "padding": "0px", + "size": "16px", + "tag": "A", + "text": "Entra con tu cuenta para añadir a una lista de reproducción", + "weight": "400", + "width": 43.3399658203125 + }, + { + "background": "rgba(0, 0, 0, 0)", + "clase": "boton svelte-1d91r2i compacto", + "color": "oklch(0.24 0.055 264)", + "font": "Archivo, ui-sans-serif, system-ui, sans-serif", + "height": 43.340087890625, + "line": "21.45px", + "padding": "8px 12px", + "size": "13px", + "tag": "BUTTON", + "text": "Añadir", + "weight": "400", + "width": 62.9322509765625 + }, + { + "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 + } +] \ No newline at end of file diff --git a/docs/assets/correcciones-visuales-2026-09-14/cancion-acciones-claro.jpg b/docs/assets/correcciones-visuales-2026-09-14/cancion-acciones-claro.jpg new file mode 100644 index 0000000..26f4cf7 Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/cancion-acciones-claro.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/cancion-acciones-oscuro.jpg b/docs/assets/correcciones-visuales-2026-09-14/cancion-acciones-oscuro.jpg new file mode 100644 index 0000000..827a5da Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/cancion-acciones-oscuro.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/catalogo-tablas-escritorio.jpg b/docs/assets/correcciones-visuales-2026-09-14/catalogo-tablas-escritorio.jpg new file mode 100644 index 0000000..f40c091 Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/catalogo-tablas-escritorio.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/catalogo-tablas-movil.jpg b/docs/assets/correcciones-visuales-2026-09-14/catalogo-tablas-movil.jpg new file mode 100644 index 0000000..0b255e3 Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/catalogo-tablas-movil.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/catalogo-tarjetas-escritorio.jpg b/docs/assets/correcciones-visuales-2026-09-14/catalogo-tarjetas-escritorio.jpg new file mode 100644 index 0000000..cec376f Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/catalogo-tarjetas-escritorio.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/catalogo-tarjetas-movil.jpg b/docs/assets/correcciones-visuales-2026-09-14/catalogo-tarjetas-movil.jpg new file mode 100644 index 0000000..afe63d4 Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/catalogo-tarjetas-movil.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/contacto-escritorio.jpg b/docs/assets/correcciones-visuales-2026-09-14/contacto-escritorio.jpg new file mode 100644 index 0000000..5ee1768 Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/contacto-escritorio.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/contacto-movil.jpg b/docs/assets/correcciones-visuales-2026-09-14/contacto-movil.jpg new file mode 100644 index 0000000..5f719aa Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/contacto-movil.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/estudio-editor-escritorio.jpg b/docs/assets/correcciones-visuales-2026-09-14/estudio-editor-escritorio.jpg new file mode 100644 index 0000000..a9d0ff8 Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/estudio-editor-escritorio.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/estudio-editor-movil.jpg b/docs/assets/correcciones-visuales-2026-09-14/estudio-editor-movil.jpg new file mode 100644 index 0000000..a67bc02 Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/estudio-editor-movil.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/estudio-editor-tableta.jpg b/docs/assets/correcciones-visuales-2026-09-14/estudio-editor-tableta.jpg new file mode 100644 index 0000000..f3c0ab9 Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/estudio-editor-tableta.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/estudio-inicio-escritorio.jpg b/docs/assets/correcciones-visuales-2026-09-14/estudio-inicio-escritorio.jpg new file mode 100644 index 0000000..0b70b07 Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/estudio-inicio-escritorio.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/estudio-listado-escritorio.jpg b/docs/assets/correcciones-visuales-2026-09-14/estudio-listado-escritorio.jpg new file mode 100644 index 0000000..5982c64 Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/estudio-listado-escritorio.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/estudio-listado-movil.jpg b/docs/assets/correcciones-visuales-2026-09-14/estudio-listado-movil.jpg new file mode 100644 index 0000000..97334e3 Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/estudio-listado-movil.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/estudio-listado-vacio-movil.jpg b/docs/assets/correcciones-visuales-2026-09-14/estudio-listado-vacio-movil.jpg new file mode 100644 index 0000000..ece60c5 Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/estudio-listado-vacio-movil.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/estudio-medios-fallo.jpg b/docs/assets/correcciones-visuales-2026-09-14/estudio-medios-fallo.jpg new file mode 100644 index 0000000..4b7170b Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/estudio-medios-fallo.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/estudio-revision-publicacion.jpg b/docs/assets/correcciones-visuales-2026-09-14/estudio-revision-publicacion.jpg new file mode 100644 index 0000000..b50bf82 Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/estudio-revision-publicacion.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/galeria-estudio-escritorio.jpg b/docs/assets/correcciones-visuales-2026-09-14/galeria-estudio-escritorio.jpg new file mode 100644 index 0000000..df8a6d0 Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/galeria-estudio-escritorio.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/galeria-estudio-movil.jpg b/docs/assets/correcciones-visuales-2026-09-14/galeria-estudio-movil.jpg new file mode 100644 index 0000000..dc9db0e Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/galeria-estudio-movil.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/galeria-publica-escritorio.jpg b/docs/assets/correcciones-visuales-2026-09-14/galeria-publica-escritorio.jpg new file mode 100644 index 0000000..5412e9d Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/galeria-publica-escritorio.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/galeria-publica-movil.jpg b/docs/assets/correcciones-visuales-2026-09-14/galeria-publica-movil.jpg new file mode 100644 index 0000000..9737783 Binary files /dev/null and b/docs/assets/correcciones-visuales-2026-09-14/galeria-publica-movil.jpg differ diff --git a/docs/assets/correcciones-visuales-2026-09-14/mediciones.json b/docs/assets/correcciones-visuales-2026-09-14/mediciones.json new file mode 100644 index 0000000..a298cf1 --- /dev/null +++ b/docs/assets/correcciones-visuales-2026-09-14/mediciones.json @@ -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 + } +] \ No newline at end of file diff --git a/docs/assets/jukebox-corregido-2026-09-14/centrado.json b/docs/assets/jukebox-corregido-2026-09-14/centrado.json new file mode 100644 index 0000000..1301175 --- /dev/null +++ b/docs/assets/jukebox-corregido-2026-09-14/centrado.json @@ -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" + } +] \ No newline at end of file diff --git a/docs/assets/jukebox-corregido-2026-09-14/escritorio.jpg b/docs/assets/jukebox-corregido-2026-09-14/escritorio.jpg new file mode 100644 index 0000000..61a8d27 Binary files /dev/null and b/docs/assets/jukebox-corregido-2026-09-14/escritorio.jpg differ diff --git a/docs/assets/jukebox-corregido-2026-09-14/movil.jpg b/docs/assets/jukebox-corregido-2026-09-14/movil.jpg new file mode 100644 index 0000000..d807000 Binary files /dev/null and b/docs/assets/jukebox-corregido-2026-09-14/movil.jpg differ diff --git a/docs/assets/jukebox-etiquetas-2026-09-14/claro.jpg b/docs/assets/jukebox-etiquetas-2026-09-14/claro.jpg new file mode 100644 index 0000000..137c4f7 Binary files /dev/null and b/docs/assets/jukebox-etiquetas-2026-09-14/claro.jpg differ diff --git a/docs/assets/jukebox-etiquetas-2026-09-14/escritorio.jpg b/docs/assets/jukebox-etiquetas-2026-09-14/escritorio.jpg new file mode 100644 index 0000000..27f2353 Binary files /dev/null and b/docs/assets/jukebox-etiquetas-2026-09-14/escritorio.jpg differ diff --git a/docs/assets/jukebox-etiquetas-2026-09-14/medidas.json b/docs/assets/jukebox-etiquetas-2026-09-14/medidas.json new file mode 100644 index 0000000..a6feed1 --- /dev/null +++ b/docs/assets/jukebox-etiquetas-2026-09-14/medidas.json @@ -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" + } + ] + } +] \ No newline at end of file diff --git a/docs/assets/jukebox-etiquetas-2026-09-14/movil.jpg b/docs/assets/jukebox-etiquetas-2026-09-14/movil.jpg new file mode 100644 index 0000000..ab7bf7d Binary files /dev/null and b/docs/assets/jukebox-etiquetas-2026-09-14/movil.jpg differ diff --git a/docs/assets/likes-2026-09-14/tarjeta-movil.jpg b/docs/assets/likes-2026-09-14/tarjeta-movil.jpg new file mode 100644 index 0000000..d8bb3e7 Binary files /dev/null and b/docs/assets/likes-2026-09-14/tarjeta-movil.jpg differ diff --git a/docs/auditoria-estilos-2026-09-13.md b/docs/auditoria-estilos-2026-09-13.md index 0169784..c21d2d6 100644 --- a/docs/auditoria-estilos-2026-09-13.md +++ b/docs/auditoria-estilos-2026-09-13.md @@ -2,6 +2,8 @@ Fecha de corte: 13 de septiembre de 2026. +> Revisión del 14 de septiembre: la [auditoría visual en navegador](auditoria-visual-2026-09-14.md) confirma incoherencias de componentes, jerarquía y tamaños efectivos que este inventario estático no detecta. La centralización descrita aquí no acredita la aprobación del diseño. El sistema visual queda pendiente de corrección y validación por función. + ## Objetivo y criterio de aceptación La aplicación debe comportarse como un único sistema visual, no como una colección de páginas con CSS independiente. Un lote de normalización solo se considera terminado cuando: diff --git a/docs/auditoria-visual-2026-09-14.md b/docs/auditoria-visual-2026-09-14.md new file mode 100644 index 0000000..32f0902 --- /dev/null +++ b/docs/auditoria-visual-2026-09-14.md @@ -0,0 +1,174 @@ +# Auditoría visual de Senza Paura — 14 de septiembre de 2026 + +## Dictamen + +**El sistema visual no está normalizado por función. El diseño no se considera cerrado.** + +La centralización de variables ha reducido los valores dispersos, pero no ha creado un contrato efectivo entre los componentes. Hay diferencias de comportamiento entre tablas equivalentes, alturas incompatibles dentro de una misma fila de acciones, pesos heredados y una jerarquía administrativa demasiado plana. Estudio necesita una revisión de su estructura visual, además de la corrección de sus controles. + +La valoración anterior de «cierre y pulido» era demasiado optimista para el estado del diseño. Las pruebas funcionales y el uso de tokens no acreditan coherencia visual. + +Esta auditoría registra el estado del commit `1e24c87`, anterior a las correcciones. La implementación y su comprobación posterior se recogen en [Correcciones visuales](correcciones-visuales-2026-09-14.md). Las capturas de este documento se conservan como evidencia del estado inicial. + +## Evidencia y cobertura + +Inspección en navegador del código local, con la web conectada a su base configurada y Estudio al esquema de pruebas existente `estudio_pruebas_20260911_nuevo`. No se han creado contenidos, publicado fichas, enviado formularios ni alterado el catálogo. Se ha utilizado el acceso de prueba existente. + +| Superficie | Revisión realizada | +| -------------- | ---------------------------------------------------------------------------------------------------------- | +| Portada | Vista de escritorio del carrusel, detenido en la diapositiva del taller | +| Música | Tarjetas y tablas; claro y oscuro en escritorio; tablas en móvil | +| Álbum | Cupido sin flechas: cabecera, acciones y tabla de temas | +| Canción | Ámame esta noche, nada más: cabecera, acciones, letra y ficha lateral | +| Blog | Índice, buscador, etiquetas y tarjetas | +| Taller | Índice, navegación lateral y jerarquía editorial | +| Intérpretes | Directorio y tarjetas | +| Suscripciones | Cabeceras y tarjetas de planes; no contratación | +| Juke-box | Hero público y sus dos acciones; no editor autenticado | +| Contacto | Diálogo abierto desde el pie, campos, foco y acción principal | +| Estudio | Acceso, inicio, listado de canciones, creación, ficha existente, revisión de publicación, pedidos y medios | +| Estudio móvil | Ficha existente a 390 × 844 | +| Panel anterior | Lectura de código del layout; no revisión autenticada en navegador | + +Escritorio: 1280 × 720 y 1280 × 900 según la captura. Móvil: 390 × 844. Las dimensiones se comprobaron en el navegador. Estudio se revisó en su presentación oscura actual; no dispone del selector de tema de la web. + +Las capturas y mediciones están en [la carpeta de evidencias](assets/auditoria-visual-2026-09-14/). Los JSON contienen estilos calculados y geometría; son inventarios, no informes de accesibilidad. También pueden incluir elementos fuera del viewport, ocultos mediante recorte o transformados por la animación de entrada. Las medidas citadas a continuación corresponden a los controles principales inspeccionados, no a esos elementos auxiliares. + +No quedan certificados todos los estados de todas las rutas: faltan, entre otros, el panel anterior autenticado, el editor Jukebox, cuenta/biblioteca, el reproductor activo, errores de validación exhaustivos y la matriz completa de móvil/tableta/temas. Esto no reduce la validez de los fallos reproducidos; impide llamar a este informe una aprobación visual integral. + +## Hallazgos prioritarios + +P1 significa que debe resolverse antes de aprobar el sistema visual. P2 significa coherencia o composición que debe corregirse dentro del mismo trabajo de diseño. No son clasificaciones de seguridad. + +### V01 · P1 · Álbumes y canciones tienen dos patrones de reproducción en tabla + +Álbumes conserva una celda exclusiva para Play antes de la portada. Canciones superpone Play sobre la portada y lo muestra con interacción, foco o reproducción. Las portadas y los títulos empiezan en posiciones distintas y la misma acción se busca en dos lugares. + +**Origen:** `src/lib/components/TablaAlbumes.svelte:29` y `:41`; `src/lib/components/TablaCanciones.svelte:255` y `:349`. `TablaCatalogo` comparte la envoltura, pero no la composición de la fila. + +**Corrección:** compartir una celda de portada/reproducción. Mantener el comportamiento de canciones en ambos listados, con acción visible en táctil y accesible con teclado. La portada no debe cambiar de tamaño al pasar de Play a Pausa. + +![Play separado en álbumes y superpuesto en canciones](assets/auditoria-visual-2026-09-14/comparacion-albumes-canciones.jpg) + +### V02 · P1 · Las acciones de una misma canción tienen alturas diferentes + +En la ficha, «Escuchar completa» mide **44 px** y «Añadir al carrito» **50,75 px**. Ambos tienen texto de 15 px y están juntos. El primero usa interlineado de 15 px; el segundo hereda 24,75 px. El relleno se suma de forma distinta. + +**Origen:** `BotonReproducir.svelte:111` y `BotonCarrito.svelte:57`. Evidencia: `web-cancion-medidas.json` y `web-cancion-claro.jpg`. + +**Corrección:** una base geométrica de botón compartida. Dos acciones del mismo tamaño deben tener la misma altura, interlineado, radio y alineación de icono, independientemente del componente que las renderice. + +### V03 · P1 · El tamaño declarado no coincide con el tamaño final de Estudio + +`.boton` declara mínimo de 44 px, pero 24 px de línea + 24 px de relleno vertical + 2 px de borde producen **50 px**. `.boton.pequeno` declara mínimo de 36 px, pero su línea y relleno producen **42 px**. El botón de búsqueda de la cabecera usa un glifo de 24 px y alcanza **52 px** de altura. + +**Origen:** `src/estudio/estudio.css:94` y `:125`; barra superior en `src/estudio/rutas/(dentro)/+layout.svelte`. Evidencia: `estudio-canciones-medidas.json`. + +**Corrección:** definir tamaños reales, con interlineado explícito y relleno compatible. Un token de `min-height` por sí solo no establece el tamaño. + +### V04 · P1 · Estudio aplana funciones distintas a 16 px + +Ayudas, `small`, antetítulos, estados, migas, encabezados de tabla y botones pequeños usan `--fs-base`. Las cabeceras de tabla añaden mayúsculas y peso 600; los valores, estados y explicaciones ocupan casi el mismo nivel. Se obliga a distinguir la información por posición en lugar de por jerarquía. + +**Origen:** `estudio.css:84`, `:87`, `:160`, `:289` y `:354`. + +**Corrección:** asignar roles completos a metadatos, etiquetas de campo, valores, títulos de registro, sección y página. La normalización debe conservar diferencias funcionales. + +### V05 · P1 · Los campos heredan negrita de sus etiquetas + +El título de una ficha y el buscador de Medios se presentan con peso 600. El buscador de Canciones usa 400. En Medios también se ve la diferencia en el placeholder. La apariencia depende de si el control está envuelto en un `label`. + +**Origen:** `estudio.css:25` (`font: inherit`) y `:209` (`label` con peso 600); solo `textarea` restablece explícitamente el peso. + +**Corrección:** separar el estilo de la etiqueta del estilo del valor. Inputs, selects, textareas y placeholders deben tener peso explícito; preferentemente 400 para la entrada. + +### V06 · P1 · Hay pesos e interlineados efectivos fuera del contrato + +El H1 de Estudio calcula 34/51 px y peso 600. Sus H2 calculan 24/36 px y **peso 700** heredado de las reglas del navegador. En la web los títulos editoriales sí reciben peso 400 e interlineado 1,1. El problema no es que una página editorial y una pantalla administrativa tengan títulos distintos: es que esa diferencia no está definida por un rol y parte del resultado queda en manos de la herencia. + +**Origen:** `estudio.css:57–76`. Evidencia: `estudio-ficha-medidas.json`. + +**Corrección:** declarar familia, tamaño, peso e interlineado de todos los roles de encabezado; no depender de los valores predeterminados de `h2`, `h3` o `strong`. + +### V07 · P1 · Conviven varias bases de botón + +La web utiliza `.btn`, botones musicales `.boton` y estilos locales. Estudio define otra `.boton`: texto de 16 px/600 y primario dorado plano, frente a 15 px/400 en acciones públicas. Los formularios públicos cambian el primario a navy/celeste según el tema mediante selectores dependientes de sus ancestros. El componente y su ubicación deciden la apariencia. + +**Origen:** `src/app.css:453`, `:545`, `:665`; `estudio.css:94`; componentes `BotonCarrito`, `BotonReproducir`, favoritos y listas. + +**Corrección:** una base compartida y variantes explícitas por importancia y contexto. La excepción del Play dorado puede conservarse, pero tiene que ser deliberada. Mover un botón dentro de un formulario no debe cambiar su diseño accidentalmente. + +### V08 · P1 · El editor dedica casi toda la primera pantalla a su cabecera + +En escritorio a 720 px de alto, el primer campo queda al borde inferior tras cabecera global, aviso de entorno, migas, antetítulo, título, estado, caja de publicación y pestañas. En móvil a 844 px apenas empieza «Datos de la ficha». Se repiten el estado y las explicaciones de publicación. + +**Origen:** layout de Estudio y `src/estudio/rutas/(dentro)/[tipo]/[id]/+page.svelte`. Evidencia: `estudio-ficha-oscuro.jpg` y `estudio-ficha-movil.jpg`. + +**Corrección:** cabecera de editor compacta con título, un estado y acciones; agrupar ayudas secundarias y hacerlas desplegables. El primer campo útil debe quedar visible en la primera pantalla normal de edición. Validar con títulos largos y avisos reales. + +![Cabecera y acciones de Estudio antes de llegar a los campos](assets/auditoria-visual-2026-09-14/estudio-ficha-oscuro.jpg) + +## Otros hallazgos de coherencia + +| ID | Prioridad | Evidencia y efecto | Corrección verificable | +| --- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| V09 | P2 | Inicio de Estudio: «Ver todas» queda junto a «Crear canción», pero debajo de las otras acciones; los títulos largos desplazan el contenido. `estudio-inicio-oscuro.jpg`; `src/estudio/rutas/(dentro)/+page.svelte:31`. | Tarjetas con las mismas filas internas y acciones ancladas abajo. Comprobar nombres de una y dos líneas. | +| V10 | P2 | Navegación de Estudio a 19 px, frente a texto principal de 16 px. Nombres partidos y menú con scroll propio; el menú compite con el trabajo. `+layout.svelte:205`. | Navegación utilitaria a 14–15 px, grupos secundarios y áreas táctiles suficientes, sin aumentar el texto para aumentar el área pulsable. | +| V11 | P2 | Los estados Publicado y Retirado comparten colores; Borrador y la selección de navegación usan el mismo dorado que las acciones. `estudio.css:178–189`. | Separar tokens de estado, selección y acción; conservar etiqueta e icono además del color. | +| V12 | P2 | La tabla de Canciones ofrece filtros sin etiquetas visibles y botón Buscar; Medios muestra etiquetas y filtrado con otro patrón. Sus inputs también difieren de peso. `estudio-canciones-oscuro.jpg`, `estudio-medios.jpg`. | Barra de filtros compartida, con la misma posición de etiquetas y una regla explícita de aplicación de filtros. | +| V13 | P2 | En Medios hay imágenes rotas que muestran icono del navegador y nombre de archivo; el audio usa el control nativo gris. Los archivos pertenecen a pruebas: no se atribuye su ausencia a producción. | Estado visual definido para archivo ausente/no disponible; contenedor y densidad comunes para vistas previas. Usar audio nativo puede ser válido si se integra deliberadamente. | +| V14 | P2 | En móvil, Música conserva la instrucción «de la izquierda» aunque los filtros estén en un botón superior. La primera portada aparece casi al final de la pantalla. `web-tablas-movil.jpg`. | Texto neutral respecto a la posición, introducción más breve y separación vertical adaptada a exploración móvil. | +| V15 | P2 | Estudio mezcla glifos de texto (`⌕`, `⌂`, `+`, `↗`, `♫`) con el sistema SVG de la web; cambian tamaño óptico y alineación. | Mismo componente de icono, cajas de 16/20 px por tamaño de control y una línea base común. | +| V16 | P2 | Los títulos de tarjetas musicales usan 24 px en álbumes y 19 px en canciones; los metadatos usan 14 frente a 12 px. La excepción no viene dada por un tipo compartido de tarjeta. `TarjetaAlbum.svelte:101`, `TarjetaCancion.svelte:123`. | Definir tarjeta normal/compacta por contexto. Un tipo de contenido no debe introducir su propia escala sin una razón de densidad documentada. | +| V17 | P1 | `node scripts/auditar-estilos.mjs --strict` termina con código 0 y «0 infracciones», mientras V01–V08 están presentes. El análisis no calcula cascada, herencia ni medidas reales y no compara funciones equivalentes. | Mantenerlo como control estático; añadir comprobación de estilos calculados y revisión visual de una galería de componentes. No usar su resultado como aprobación del diseño. | + +## Qué conservar y qué no confundir con un fallo + +Las familias Archivo y Newsreader y la base marino/dorado proporcionan una identidad reconocible. Las cabeceras de Música, Blog y Taller comparten ya 48 px/400; las tablas de canciones integran la reproducción en la portada y contemplan foco y táctil. Las tarjetas de intérpretes alinean sus acciones inferiores mejor que las del inicio de Estudio. + +Un hero editorial puede tener una escala distinta de un editor. La marca puede aparecer en serif mientras la administración utiliza una jerarquía utilitaria. La normalización no exige igualar todos los encabezados HTML, sino dar el mismo tratamiento a las funciones equivalentes y documentar las excepciones. + +## Contrato propuesto para la corrección + +Propuesta de trabajo, todavía no aplicada. Usar los tokens primitivos existentes como base; añadir roles que agrupen familia, tamaño, peso e interlineado. Evitar que cada pantalla seleccione estas cuatro propiedades de forma independiente. + +### Tipografía por función + +| Función | Familia | Tamaño / interlineado | Peso | +| --------------------------------------------- | ------------------------ | ------------------------------ | --------------------------------- | +| Título de página editorial | Newsreader | 48/53 escritorio; 34/39 móvil | 400 | +| Hero de marca | Newsreader | Escala fluida propia y acotada | 400 | +| Título de página administrativa | Archivo | 28/34 escritorio; 24/30 móvil | 600 | +| Sección editorial | Newsreader | 34/38 | 400 | +| Sección administrativa / tarjeta de gestión | Archivo | 20/26 | 600 | +| Panel auxiliar / diálogo compacto | Archivo | 18/24 | 600 | +| Título de registro o tarjeta compacta | Archivo | 16/22 | 500 | +| Texto de lectura | Según contexto editorial | 16–19, línea 1,6–1,7 | 400 | +| Valor de formulario | Archivo | 16/24 | 400 | +| Etiqueta de campo, navegación y dato de tabla | Archivo | 14/20 | 500 para etiqueta; 400 para valor | +| Ayuda y metadatos | Archivo | 13/18 | 400 | +| Antetítulo y cabecera de tabla | Archivo | 12/16 | 500 | + +La elección de Archivo para títulos administrativos es una propuesta de dirección, no un defecto objetivo de Newsreader. Debe validarse en el conjunto antes de extenderla. + +### Botones y controles + +| Tamaño | Altura final normal | Texto | Icono | Uso | +| --------- | ------------------- | --------------- | ----- | -------------------------------------------- | +| Compacto | 36 px | 14/20, peso 500 | 16 px | Tablas, herramientas y tarjetas densas | +| Normal | 44 px | 14/20, peso 500 | 20 px | Formularios y acciones de página | +| Destacado | 48 px | 16/22, peso 500 | 20 px | CTA de hero cuando lo justifique el contexto | + +Radio compartido de 6 px; relleno horizontal según tamaño; borde incluido en el cálculo. En táctil, asegurar una zona de interacción de al menos 44 px sin agrandar necesariamente el símbolo. Con ampliación de texto, permitir crecimiento accesible en lugar de cortar las etiquetas para mantener una altura rígida. + +Variantes explícitas: primaria, secundaria, discreta, destructiva y reproducción de marca. Todas deben definir reposo, hover, foco, pulsado, deshabilitado y carga. El estado de carga conserva el ancho y la alineación. Los segmentos seleccionados y las etiquetas informativas no heredan automáticamente el tratamiento del botón primario. + +## Orden de implementación y aceptación + +1. **Base y galería:** crear una página local de referencia con roles tipográficos, botones, campos, estados, tablas y tarjetas. Mostrar juntos los equivalentes de web y Estudio. Revisar el resultado renderizado antes de migrar pantallas. +2. **Estudio:** corregir herencia y encabezados, tamaños reales de controles, navegación, filtros, tarjetas de inicio y cabecera del editor. Resolver V03–V12 y V15 con una matriz de listado/edición/diálogo/vacío. +3. **Catálogo y fichas:** compartir celda de reproducción, base de botones y tarjetas. Resolver V01, V02 y V16 con Play/Pausa, foco, táctil y títulos largos. +4. **Resto público:** aplicar los roles a formulario, navegación, blog, taller, planes y Jukebox; conservar las excepciones editoriales justificadas. Revisar V14 y los estados no cubiertos. +5. **Cierre visual:** capturas comparables a 390, 768, 1024 y 1440 px, ambos temas donde existan; teclado, carga, error y vacío. Comparar estilos calculados además de imágenes. La ausencia de overflow o de errores de Svelte no sustituye esta revisión. + +La aprobación exige que las acciones equivalentes compartan geometría, que no aparezcan pesos accidentales, que los valores de formulario no hereden el énfasis de sus etiquetas y que la tarea principal sea visible sin atravesar bloques repetidos de explicación. El diseño permanece **pendiente de corrección y validación visual** hasta entonces. diff --git a/docs/correcciones-jukebox-2026-09-14.md b/docs/correcciones-jukebox-2026-09-14.md new file mode 100644 index 0000000..ba1b5d6 --- /dev/null +++ b/docs/correcciones-jukebox-2026-09-14.md @@ -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) diff --git a/docs/correcciones-visuales-2026-09-14.md b/docs/correcciones-visuales-2026-09-14.md new file mode 100644 index 0000000..2d06eea --- /dev/null +++ b/docs/correcciones-visuales-2026-09-14.md @@ -0,0 +1,52 @@ +# Correcciones visuales — 14 de septiembre de 2026 + +Implementación local de los problemas encontrados en la [auditoría visual](auditoria-visual-2026-09-14.md). No se ha desplegado ni publicado contenido. Estudio se ha revisado con su esquema de pruebas existente. + +## Cambios + +| Hallazgos | Implementación | +| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| V01 | `PortadaReproducible` compartida por las tablas de canciones y álbumes. Una sola celda de portada/reproducción, con Play/Pausa, foco, estado activo y alternativa sin imagen. | +| V02, V03, V07 | `controles.css` define la geometría y tipografía de acciones públicas, musicales y administrativas. Las variantes de formulario se declaran explícitamente; su color no depende del ancestro. Incluye carrito, favoritos, cola, listas y herramientas del editor. | +| V04–V06 | Roles completos de título, sección, panel, etiqueta, metadato y valor. Estudio usa Archivo 28/33,6–600 para página, 20/26–600 para sección y 18/24,3–600 para panel. Los campos tienen peso 400 y las etiquetas 500. También se normalizan los campos públicos de acceso, búsqueda y contacto. | +| V08 | Cabecera más corta; un único estado de edición, con explicación desplegable. La ayuda de guardado y el aviso de entorno también se pueden desplegar. Se conservan mensajes de operación, errores y revisión previa a publicar. | +| V09, V10 | Acciones alineadas al pie de los tres accesos de inicio. Navegación a 14 px, con altura de interacción independiente de la letra. | +| V11 | Publicado verde, borrador azul y retirado neutro, conservando texto y marcador. Las acciones de administración usan azul; la reproducción musical conserva oro. | +| V12 | Etiquetas visibles y patrón de campos común en listados, búsqueda, operaciones y medios. Medios identifica expresamente su filtrado al escribir. Los otros filtros conservan su envío GET. | +| V13 | Vista previa de medios con estado «Archivo no disponible». El audio mantiene controles nativos en un contenedor definido. | +| V14 | Introducción de Música más corta y sin referencias a «la izquierda»; título móvil a 34 px. | +| V15 | SVG del catálogo de iconos en cabecera, listados, creación, relaciones y herramientas de secciones. | +| V16 | Los títulos de tarjetas de álbum y canción comparten Newsreader 19/20,9–400; metadatos de ambas a 13 px. | +| V17 | Galería local en `/__diseno` en la web y Estudio. Incluye variantes, etiquetas largas, foco, campos y estados; permite comprobar estilos calculados y desbordamientos. Responde 404 fuera de desarrollo. | + +## Evidencia del resultado + +| Comprobación | Antes | Después medido | +| ---------------------------------- | -------------------------------------- | ------------------------------------------------------------------- | +| Escuchar / carrito en ficha | 44 / 50,75 px; interlíneas diferentes | Ambos 44 px, Archivo 14/20–500 | +| Botón normal / compacto de Estudio | 50 / 42 px | 44 / 36 px | +| Buscar en cabecera de Estudio | 52 px y glifo tipográfico | 44 px y SVG de 20 px | +| Valor de campo dentro de etiqueta | Peso 600 heredado | Archivo 16 px, peso 400 | +| Secciones H2 de Estudio | 24/36–700 | 20/26–600 | +| Portadas de ambas tablas | Celda de Play independiente en álbumes | Una portada con un control; 80 × 80 en escritorio, 56 × 56 en móvil | +| Primer campo del editor móvil | Quedaba fuera de la primera pantalla | Empieza en y=605 a 390 × 844, sin desbordamiento de la página | +| Acciones de los accesos de inicio | Distinta colocación | Las tres filas de acciones comparten la misma coordenada vertical | + +El compacto crece a 44 px para punteros táctiles mediante CSS. Las alturas son mínimas y las etiquetas multilínea pueden crecer; no se recorta el texto para conservar una medida rígida. + +![Inicio de Estudio](assets/correcciones-visuales-2026-09-14/estudio-inicio-escritorio.jpg) + +![Editor de Estudio](assets/correcciones-visuales-2026-09-14/estudio-editor-escritorio.jpg) + +![Catálogo móvil](assets/correcciones-visuales-2026-09-14/catalogo-tablas-movil.jpg) + +Más capturas y [mediciones JSON](assets/correcciones-visuales-2026-09-14/mediciones.json) en `docs/assets/correcciones-visuales-2026-09-14/`. + +## Verificación y límites + +- Web: tarjetas y tablas del catálogo en escritorio y móvil; reproducción y pausa desde portada de álbum; ficha de canción en temas claro y oscuro; foco de teclado; campos de contacto; galería con etiquetas largas y controles deshabilitados. +- Estudio: inicio, filtros del listado con resultado y vacío, editor a 390, 768 y 1280 px, diálogo de revisión de publicación, biblioteca y archivo dañado. Los formularios de guardado y publicación no se enviaron. +- Comprobador de la galería sin desviaciones en la web y Estudio; Estudio comprobado a 390, 768 y 1280 px. Las mediciones de viewport no equivalen a una prueba en dispositivo físico táctil. +- `npm run check`: web, panel anterior y Estudio, sin errores ni advertencias de Svelte. Repetidos los checks afectados después de las últimas modificaciones. ESLint de los archivos modificados, Prettier y `audit:styles -- --strict` correctos. + +Esto acredita las superficies y estados indicados, no una aprobación estética global. No se ha repetido la matriz completa de todos los contenidos, todos los anchos y todos los estados autenticados de compras/Jukebox, ni una revisión visual autenticada del panel anterior. La auditoría estática sigue siendo complementaria: no sustituye las capturas ni los estilos calculados. diff --git a/docs/jukebox-emociones-2026-09-14.md b/docs/jukebox-emociones-2026-09-14.md new file mode 100644 index 0000000..6d5b700 --- /dev/null +++ b/docs/jukebox-emociones-2026-09-14.md @@ -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) diff --git a/docs/likes-2026-09-14.md b/docs/likes-2026-09-14.md new file mode 100644 index 0000000..e67d93a --- /dev/null +++ b/docs/likes-2026-09-14.md @@ -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). diff --git a/docs/modelo-datos.md b/docs/modelo-datos.md new file mode 100644 index 0000000..9399fdf --- /dev/null +++ b/docs/modelo-datos.md @@ -0,0 +1,1927 @@ +# Modelo de datos completo de Senza Paura + +Generado el 2026-09-14T20:26:39.479Z desde el esquema PostgreSQL aplicado y contrastado con los contratos TypeScript de la aplicación. + +**Alcance:** 83 tablas, 1 vista, 3 secuencias, 4 funciones de base y 42 migraciones aplicadas. + +## 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`. + +## 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 +``` + +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. + +## 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. + +## 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 +``` + +## 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. + +## Base y artistas + +### `artista` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| -------------------- | -------------------------- | :--: | ----------------------------------------------------------------------------------------- | +| `id` | `text` | no | — | +| `codigo` | `text` | no | `('ART-'::text \|\| lpad((nextval('artista_codigo_seq'::regclass))::text, 6, '0'::text))` | +| `slug` | `text` | no | — | +| `tipo` | `text` | no | `'persona'::text` | +| `nombre` | `text` | no | — | +| `descripcion_corta` | `text` | no | `''::text` | +| `biografia` | `text` | no | `''::text` | +| `imagen_id` | `text` | sí | — | +| `pais_codigo` | `character(2)` | sí | — | +| `lugar_origen` | `text` | sí | — | +| `inicio` | `integer` | sí | — | +| `fin` | `integer` | sí | — | +| `sitio_web` | `text` | sí | — | +| `estado` | `text` | no | `'borrador'::text` | +| `origen` | `text` | no | `'manual'::text` | +| `modelo_origen` | `text` | sí | — | +| `importado_en` | `timestamp with time zone` | sí | — | +| `revisado_por` | `text` | sí | — | +| `revisado_en` | `timestamp with time zone` | sí | — | +| `creado_en` | `timestamp with time zone` | no | `now()` | +| `actualizado_en` | `timestamp with time zone` | sí | — | +| `secciones` | `jsonb` | no | `'[]'::jsonb` | +| `presentacion` | `jsonb` | no | `'{}'::jsonb` | +| `es_virtual` | `boolean` | no | `false` | +| `nombre_visual` | `text` | no | `'texto'::text` | +| `logotipo_id` | `text` | sí | — | +| `logotipo_claro_id` | `text` | sí | — | +| `logotipo_oscuro_id` | `text` | sí | — | + +Claves y restricciones: + +- **CHECK artista_estado_conocido:** `CHECK (estado = ANY (ARRAY['borrador'::text, 'publicado'::text, 'retirado'::text]))` +- **CHECK artista_nombre_visual_conocido:** `CHECK (nombre_visual = ANY (ARRAY['texto'::text, 'logotipo'::text]))` +- **CHECK artista_origen_conocido:** `CHECK (origen = ANY (ARRAY['manual'::text, 'importado'::text]))` +- **CHECK artista_periodo_valido:** `CHECK (fin IS NULL OR inicio IS NULL OR fin >= inicio)` +- **CHECK artista_presentacion_objeto:** `CHECK (jsonb_typeof(presentacion) = 'object'::text)` +- **CHECK artista_secciones_array:** `CHECK (jsonb_typeof(secciones) = 'array'::text)` +- **CHECK artista_tipo_conocido:** `CHECK (tipo = ANY (ARRAY['persona'::text, 'grupo'::text, 'proyecto'::text, 'virtual'::text]))` +- **FK artista_imagen_fk:** `FOREIGN KEY (imagen_id) REFERENCES medio(id) ON DELETE SET NULL` +- **FK artista_logotipo_claro_id_medio_id_fk:** `FOREIGN KEY (logotipo_claro_id) REFERENCES medio(id) ON DELETE SET NULL` +- **FK artista_logotipo_id_medio_id_fk:** `FOREIGN KEY (logotipo_id) REFERENCES medio(id) ON DELETE SET NULL` +- **FK artista_logotipo_oscuro_id_medio_id_fk:** `FOREIGN KEY (logotipo_oscuro_id) REFERENCES medio(id) ON DELETE SET NULL` +- **FK artista_pais_fk:** `FOREIGN KEY (pais_codigo) REFERENCES pais(codigo)` +- **PK artista_pkey:** `PRIMARY KEY (id)` + +Índices adicionales: `artista_codigo_unico`, `artista_estado`, `artista_nombre`, `artista_slug_unico`. + +### `artista_identificador` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | --------------- | :--: | ----------------- | +| `artista_id` | `text` | no | — | +| `sistema` | `text` | no | — | +| `valor` | `text` | no | — | + +Claves y restricciones: + +- **FK artista_identificador_artista_fk:** `FOREIGN KEY (artista_id) REFERENCES artista(id) ON DELETE CASCADE` +- **PK artista_identificador_pk:** `PRIMARY KEY (artista_id, sistema)` + +Índices adicionales: `artista_identificador_externo_unico`. + +### `artista_persona` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | --------------- | :--: | ----------------- | +| `artista_id` | `text` | no | — | +| `persona_id` | `text` | no | — | +| `relacion` | `text` | no | — | +| `desde` | `integer` | sí | — | +| `hasta` | `integer` | sí | — | +| `orden` | `integer` | no | `0` | + +Claves y restricciones: + +- **CHECK artista_persona_periodo_valido:** `CHECK (hasta IS NULL OR desde IS NULL OR hasta >= desde)` +- **CHECK artista_persona_relacion_conocida:** `CHECK (relacion = ANY (ARRAY['identidad'::text, 'miembro'::text, 'fundador'::text]))` +- **FK artista_persona_artista_fk:** `FOREIGN KEY (artista_id) REFERENCES artista(id) ON DELETE CASCADE` +- **FK artista_persona_persona_fk:** `FOREIGN KEY (persona_id) REFERENCES persona(id) ON DELETE CASCADE` +- **PK artista_persona_pk:** `PRIMARY KEY (artista_id, persona_id, relacion)` + +Índices adicionales: `artista_persona_persona`. + +### `medio` + +Archivo administrado: imagen, audio, muestra o documento, con metadatos técnicos e integridad SHA-256. + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | -------------------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `clase` | `text` | no | — | +| `clave` | `text` | no | — | +| `publico` | `boolean` | no | `false` | +| `mime` | `text` | no | — | +| `bytes` | `integer` | no | — | +| `duracion_s` | `integer` | sí | — | +| `ancho` | `integer` | sí | — | +| `alto` | `integer` | sí | — | +| `sha256` | `text` | no | — | +| `alt` | `text` | sí | — | +| `creado_en` | `timestamp with time zone` | no | `now()` | + +Claves y restricciones: + +- **PK medio_pkey:** `PRIMARY KEY (id)` +- **UNIQUE medio_clave_unique:** `UNIQUE (clave)` + +Índices adicionales: `medio_clase`, `medio_sha`. + +### `pais` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | --------------- | :--: | ----------------- | +| `codigo` | `character(2)` | no | — | +| `nombre` | `text` | no | — | +| `gentilicio` | `text` | sí | — | +| `region` | `text` | sí | — | + +Claves y restricciones: + +- **PK pais_pkey:** `PRIMARY KEY (codigo)` + +### `persona` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ---------------- | -------------------------- | :--: | ------------------ | +| `id` | `text` | no | — | +| `slug` | `text` | no | — | +| `nombre` | `text` | no | — | +| `nombre_legal` | `text` | sí | — | +| `ipi` | `text` | sí | — | +| `nacimiento` | `integer` | sí | — | +| `defuncion` | `integer` | sí | — | +| `pais_codigo` | `character(2)` | sí | — | +| `retrato_id` | `text` | sí | — | +| `semblanza` | `text` | sí | — | +| `estado` | `text` | no | `'borrador'::text` | +| `creado_en` | `timestamp with time zone` | no | `now()` | +| `actualizado_en` | `timestamp with time zone` | sí | — | +| `origen` | `text` | no | `'manual'::text` | +| `modelo_origen` | `text` | sí | — | +| `importado_en` | `timestamp with time zone` | sí | — | +| `revisado_por` | `text` | sí | — | +| `revisado_en` | `timestamp with time zone` | sí | — | + +Claves y restricciones: + +- **FK persona_pais_codigo_pais_codigo_fk:** `FOREIGN KEY (pais_codigo) REFERENCES pais(codigo)` +- **FK persona_retrato_id_medio_id_fk:** `FOREIGN KEY (retrato_id) REFERENCES medio(id) ON DELETE SET NULL` +- **PK persona_pkey:** `PRIMARY KEY (id)` +- **UNIQUE persona_slug_unique:** `UNIQUE (slug)` + +Índices adicionales: `persona_nombre`, `persona_pais`. + +## Catálogo musical + +### `album` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| --------------------- | -------------------------- | :--: | ------------------ | +| `id` | `text` | no | — | +| `slug` | `text` | no | — | +| `titulo` | `text` | no | — | +| `estilo_id` | `text` | no | — | +| `tipo` | `text` | no | `'album'::text` | +| `fecha` | `text` | no | — | +| `portada_id` | `text` | sí | — | +| `sello` | `text` | sí | — | +| `resumen` | `text` | no | `''::text` | +| `destacado` | `boolean` | no | `false` | +| `numero_catalogo` | `text` | sí | — | +| `formatos` | `text[]` | sí | — | +| `pais_edicion_codigo` | `character(2)` | sí | — | +| `estado` | `text` | no | `'borrador'::text` | +| `creado_en` | `timestamp with time zone` | no | `now()` | +| `actualizado_en` | `timestamp with time zone` | sí | — | +| `cuerpo` | `text` | no | `''::text` | +| `secciones` | `jsonb` | no | `'[]'::jsonb` | +| `presentacion` | `jsonb` | no | `'{}'::jsonb` | + +Claves y restricciones: + +- **CHECK album_presentacion_objeto:** `CHECK (jsonb_typeof(presentacion) = 'object'::text)` +- **CHECK album_secciones_array:** `CHECK (jsonb_typeof(secciones) = 'array'::text)` +- **FK album_estilo_id_estilo_id_fk:** `FOREIGN KEY (estilo_id) REFERENCES estilo(id)` +- **FK album_pais_edicion_codigo_pais_codigo_fk:** `FOREIGN KEY (pais_edicion_codigo) REFERENCES pais(codigo)` +- **FK album_portada_id_medio_id_fk:** `FOREIGN KEY (portada_id) REFERENCES medio(id) ON DELETE SET NULL` +- **PK album_pkey:** `PRIMARY KEY (id)` +- **UNIQUE album_slug_unique:** `UNIQUE (slug)` + +Índices adicionales: `album_estilo`, `album_fecha`. + +### `album_enlace` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | --------------- | :--: | ----------------- | +| `album_id` | `text` | no | — | +| `plataforma` | `text` | no | — | +| `url` | `text` | no | — | + +Claves y restricciones: + +- **FK album_enlace_album_id_album_id_fk:** `FOREIGN KEY (album_id) REFERENCES album(id) ON DELETE CASCADE` +- **PK album_enlace_album_id_plataforma_pk:** `PRIMARY KEY (album_id, plataforma)` + +### `autoria` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | --------------- | :--: | ----------------- | +| `cancion_id` | `text` | no | — | +| `persona_id` | `text` | no | — | +| `rol` | `text` | no | — | +| `porcentaje` | `numeric(5,2)` | no | — | + +Claves y restricciones: + +- **FK autoria_cancion_id_cancion_id_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE CASCADE` +- **FK autoria_persona_id_persona_id_fk:** `FOREIGN KEY (persona_id) REFERENCES persona(id)` +- **PK autoria_cancion_id_persona_id_rol_pk:** `PRIMARY KEY (cancion_id, persona_id, rol)` +- **t autoria_suma_cien_trg:** `TRIGGER DEFERRABLE INITIALLY DEFERRED` + +Disparadores: + +- `autoria_suma_cien_trg`: `CREATE CONSTRAINT TRIGGER autoria_suma_cien_trg AFTER INSERT OR DELETE OR UPDATE ON autoria DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION autoria_suma_cien()` + +### `cancion` + +Tema principal o versión musical. `version_de_id` apunta a otra canción y separa obra principal de arreglo o grabación. + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ----------------------- | -------------------------- | :--: | ----------------------------------------------------------------------------------------- | +| `id` | `text` | no | — | +| `slug` | `text` | no | — | +| `titulo` | `text` | no | — | +| `version_de_id` | `text` | sí | — | +| `version_nombre` | `text` | sí | — | +| `version_orden` | `integer` | no | `0` | +| `estilo_id` | `text` | no | — | +| `album_id` | `text` | sí | — | +| `numero` | `integer` | sí | — | +| `duracion_s` | `integer` | sí | — | +| `ano` | `integer` | sí | — | +| `letra` | `text` | sí | — | +| `idioma` | `text` | no | — | +| `inspiracion` | `text` | sí | — | +| `tiene_letra` | `boolean` | no | `true` | +| `destacada` | `boolean` | no | `false` | +| `portada_id` | `text` | sí | — | +| `ala_venta` | `boolean` | no | `false` | +| `estado` | `text` | no | `'borrador'::text` | +| `creado_en` | `timestamp with time zone` | no | `now()` | +| `actualizado_en` | `timestamp with time zone` | sí | — | +| `es_version` | `boolean` | sí | `(version_de_id IS NOT NULL)` | +| `padre_no_es_version` | `boolean` | sí | ` CASE WHEN (version_de_id IS NULL) THEN NULL::boolean ELSE false END` | +| `version_nota` | `text` | sí | — | +| `codigo` | `text` | no | `('CAN-'::text \|\| lpad((nextval('cancion_codigo_seq'::regclass))::text, 6, '0'::text))` | +| `version_codigo` | `text` | sí | — | +| `formacion_vocal` | `formacion_vocal` | sí | — | +| `acceso` | `acceso_cancion` | no | `'publico'::acceso_cancion` | +| `inspiracion_secciones` | `jsonb` | no | `'[]'::jsonb` | +| `presentacion` | `jsonb` | no | `'{}'::jsonb` | + +Claves y restricciones: + +- **CHECK cancion_estado_conocido:** `CHECK (estado = ANY (ARRAY['borrador'::text, 'publicado'::text, 'retirado'::text]))` +- **CHECK cancion_idioma_bcp47:** `CHECK (idioma ~ '^[a-z]{2,8}(-[A-Za-z0-9]{1,8})*$'::text)` +- **CHECK cancion_inspiracion_secciones_array:** `CHECK (jsonb_typeof(inspiracion_secciones) = 'array'::text)` +- **CHECK cancion_no_es_su_propia_version:** `CHECK (version_de_id IS NULL OR version_de_id <> id)` +- **CHECK cancion_presentacion_objeto:** `CHECK (jsonb_typeof(presentacion) = 'object'::text)` +- **CHECK cancion_version_codigo_presente:** `CHECK (version_de_id IS NULL AND version_codigo IS NULL OR version_de_id IS NOT NULL AND NULLIF(btrim(version_codigo), ''::text) IS NOT NULL)` +- **CHECK cancion_version_no_se_vende:** `CHECK (version_de_id IS NULL OR NOT ala_venta)` +- **FK cancion_album_id_album_id_fk:** `FOREIGN KEY (album_id) REFERENCES album(id) ON DELETE SET NULL` +- **FK cancion_estilo_id_estilo_id_fk:** `FOREIGN KEY (estilo_id) REFERENCES estilo(id)` +- **FK cancion_portada_id_medio_id_fk:** `FOREIGN KEY (portada_id) REFERENCES medio(id) ON DELETE SET NULL` +- **FK cancion_version_de_fk:** `FOREIGN KEY (version_de_id) REFERENCES cancion(id) ON DELETE CASCADE` +- **FK cancion_version_de_principal:** `FOREIGN KEY (version_de_id, padre_no_es_version) REFERENCES cancion(id, es_version)` +- **PK cancion_pkey:** `PRIMARY KEY (id)` +- **t cancion_publicable:** `TRIGGER DEFERRABLE INITIALLY DEFERRED` +- **UNIQUE cancion_id_es_version_unica:** `UNIQUE (id, es_version)` +- **UNIQUE cancion_slug_unique:** `UNIQUE (slug)` + +Índices adicionales: `cancion_album`, `cancion_codigo_unico`, `cancion_destacada`, `cancion_estilo`, `cancion_version_codigo_unico`, `cancion_version_de`. + +Disparadores: + +- `cancion_publicable`: `CREATE CONSTRAINT TRIGGER cancion_publicable AFTER INSERT OR UPDATE ON cancion DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION guardar_invariantes_publicacion()` + +### `cancion_audio` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| --------------- | --------------- | :--: | ----------------- | +| `cancion_id` | `text` | no | — | +| `medio_id` | `text` | no | — | +| `uso` | `text` | no | — | +| `formato` | `text` | no | — | +| `bitrate_kbps` | `integer` | sí | — | +| `frecuencia_hz` | `integer` | sí | — | +| `bits_muestra` | `integer` | sí | — | +| `canales` | `integer` | sí | — | +| `orden` | `integer` | no | `0` | + +Claves y restricciones: + +- **CHECK cancion_audio_bitrate_valido:** `CHECK (bitrate_kbps IS NULL OR bitrate_kbps > 0)` +- **CHECK cancion_audio_bits_validos:** `CHECK (bits_muestra IS NULL OR bits_muestra > 0)` +- **CHECK cancion_audio_canales_validos:** `CHECK (canales IS NULL OR canales > 0)` +- **CHECK cancion_audio_frecuencia_valida:** `CHECK (frecuencia_hz IS NULL OR frecuencia_hz > 0)` +- **CHECK cancion_audio_uso_conocido:** `CHECK (uso = ANY (ARRAY['stream'::text, 'descarga'::text, 'master'::text]))` +- **FK cancion_audio_cancion_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE CASCADE` +- **FK cancion_audio_medio_fk:** `FOREIGN KEY (medio_id) REFERENCES medio(id) ON DELETE RESTRICT` +- **PK cancion_audio_pk:** `PRIMARY KEY (cancion_id, uso, formato)` +- **t cancion_audios_publicables:** `TRIGGER DEFERRABLE INITIALLY DEFERRED` + +Índices adicionales: `cancion_audio_cancion`, `cancion_audio_medio_unico`. + +Disparadores: + +- `cancion_audios_publicables`: `CREATE CONSTRAINT TRIGGER cancion_audios_publicables AFTER INSERT OR DELETE OR UPDATE ON cancion_audio DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION guardar_invariantes_publicacion()` + +### `cancion_enlace` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | --------------- | :--: | ----------------- | +| `cancion_id` | `text` | no | — | +| `plataforma` | `text` | no | — | +| `url` | `text` | no | — | + +Claves y restricciones: + +- **FK cancion_enlace_cancion_id_cancion_id_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE CASCADE` +- **PK cancion_enlace_cancion_id_plataforma_pk:** `PRIMARY KEY (cancion_id, plataforma)` + +### `cancion_etiqueta` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------- | --------------- | :--: | ----------------- | +| `cancion_id` | `text` | no | — | +| `etiqueta_id` | `text` | no | — | +| `orden` | `integer` | no | `0` | + +Claves y restricciones: + +- **FK cancion_etiqueta_cancion_id_cancion_id_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE CASCADE` +- **FK cancion_etiqueta_etiqueta_id_etiqueta_id_fk:** `FOREIGN KEY (etiqueta_id) REFERENCES etiqueta(id) ON DELETE CASCADE` +- **PK cancion_etiqueta_cancion_id_etiqueta_id_pk:** `PRIMARY KEY (cancion_id, etiqueta_id)` + +### `cancion_interprete` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ----------------- | --------------- | :--: | ------------------- | +| `cancion_id` | `text` | no | — | +| `artista_id` | `text` | no | — | +| `rol` | `text` | no | `'principal'::text` | +| `instrumento` | `text` | sí | — | +| `acreditado_como` | `text` | sí | — | +| `orden` | `integer` | no | `0` | + +Claves y restricciones: + +- **CHECK cancion_interprete_rol_conocido:** `CHECK (rol = ANY (ARRAY['principal'::text, 'invitado'::text, 'coros'::text, 'instrumentista'::text]))` +- **FK cancion_interprete_artista_fk:** `FOREIGN KEY (artista_id) REFERENCES artista(id)` +- **FK cancion_interprete_cancion_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE CASCADE` +- **PK cancion_interprete_pk:** `PRIMARY KEY (cancion_id, artista_id, rol)` +- **t cancion_creditos_publicables:** `TRIGGER DEFERRABLE INITIALLY DEFERRED` + +Índices adicionales: `cancion_interprete_artista`. + +Disparadores: + +- `cancion_creditos_publicables`: `CREATE CONSTRAINT TRIGGER cancion_creditos_publicables AFTER INSERT OR DELETE OR UPDATE ON cancion_interprete DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION guardar_invariantes_publicacion()` + +### `cancion_persona` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | --------------- | :--: | ----------------- | +| `cancion_id` | `text` | no | — | +| `persona_id` | `text` | no | — | +| `rol` | `text` | no | — | +| `orden` | `integer` | no | `0` | + +Claves y restricciones: + +- **FK cancion_persona_cancion_id_cancion_id_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE CASCADE` +- **FK cancion_persona_persona_id_persona_id_fk:** `FOREIGN KEY (persona_id) REFERENCES persona(id)` +- **PK cancion_persona_cancion_id_persona_id_rol_pk:** `PRIMARY KEY (cancion_id, persona_id, rol)` + +### `credito_album` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | --------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `album_id` | `text` | no | — | +| `persona_id` | `text` | sí | — | +| `nombre` | `text` | sí | — | +| `rol` | `text` | no | — | +| `orden` | `integer` | no | `0` | + +Claves y restricciones: + +- **FK credito_album_album_id_album_id_fk:** `FOREIGN KEY (album_id) REFERENCES album(id) ON DELETE CASCADE` +- **FK credito_album_persona_id_persona_id_fk:** `FOREIGN KEY (persona_id) REFERENCES persona(id)` +- **PK credito_album_pkey:** `PRIMARY KEY (id)` + +Índices adicionales: `credito_album_album`. + +### `estilo` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ---------------- | -------------------------- | :--: | ------------------ | +| `id` | `text` | no | — | +| `slug` | `text` | no | — | +| `nombre` | `text` | no | — | +| `resumen` | `text` | no | `''::text` | +| `orden` | `integer` | no | `0` | +| `principal` | `boolean` | no | `false` | +| `imagen_id` | `text` | sí | — | +| `estado` | `text` | no | `'borrador'::text` | +| `creado_en` | `timestamp with time zone` | no | `now()` | +| `actualizado_en` | `timestamp with time zone` | sí | — | +| `cuerpo` | `text` | no | `''::text` | + +Claves y restricciones: + +- **FK estilo_imagen_id_medio_id_fk:** `FOREIGN KEY (imagen_id) REFERENCES medio(id) ON DELETE SET NULL` +- **PK estilo_pkey:** `PRIMARY KEY (id)` +- **UNIQUE estilo_slug_unique:** `UNIQUE (slug)` + +Índices adicionales: `estilo_orden`, `estilo_principal_unico`. + +### `etiqueta` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| -------- | --------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `slug` | `text` | no | — | +| `nombre` | `text` | no | — | + +Claves y restricciones: + +- **PK etiqueta_pkey:** `PRIMARY KEY (id)` +- **UNIQUE etiqueta_slug_unique:** `UNIQUE (slug)` + +### `registro_obra` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | --------------- | :--: | ----------------- | +| `cancion_id` | `text` | no | — | +| `iswc` | `text` | sí | — | +| `isrc` | `text` | sí | — | +| `entidad` | `text` | sí | — | +| `numero` | `text` | sí | — | +| `editorial` | `text` | sí | — | + +Claves y restricciones: + +- **FK registro_obra_cancion_id_cancion_id_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE CASCADE` +- **PK registro_obra_pkey:** `PRIMARY KEY (cancion_id)` + +### `traduccion` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | --------------- | :--: | ----------------- | +| `cancion_id` | `text` | no | — | +| `idioma` | `text` | no | — | +| `texto` | `text` | no | — | + +Claves y restricciones: + +- **FK traduccion_cancion_id_cancion_id_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE CASCADE` +- **PK traduccion_cancion_id_idioma_pk:** `PRIMARY KEY (cancion_id, idioma)` + +### `video` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | --------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `cancion_id` | `text` | no | — | +| `youtube_id` | `text` | no | — | +| `titulo` | `text` | no | — | +| `orden` | `integer` | no | `0` | + +Claves y restricciones: + +- **FK video_cancion_id_cancion_id_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE CASCADE` +- **PK video_pkey:** `PRIMARY KEY (id)` + +Índices adicionales: `video_cancion`. + +## Cuentas y suscripciones + +### `codigo_acceso` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ----------- | -------------------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `email` | `text` | no | — | +| `hash` | `text` | no | — | +| `expira_en` | `timestamp with time zone` | no | — | +| `intentos` | `integer` | no | `0` | +| `usado_en` | `timestamp with time zone` | sí | — | +| `creado_en` | `timestamp with time zone` | no | `now()` | + +Claves y restricciones: + +- **PK codigo_acceso_pkey:** `PRIMARY KEY (id)` + +Índices adicionales: `codigo_acceso_email`. + +### `identidad` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------------ | -------------------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `proveedor` | `text` | no | — | +| `sujeto` | `text` | no | — | +| `usuario_id` | `text` | no | — | +| `email` | `text` | sí | — | +| `creada_en` | `timestamp with time zone` | no | `now()` | +| `ultimo_acceso_en` | `timestamp with time zone` | sí | — | + +Claves y restricciones: + +- **FK identidad_usuario_id_usuario_id_fk:** `FOREIGN KEY (usuario_id) REFERENCES usuario(id) ON DELETE CASCADE` +- **PK identidad_pkey:** `PRIMARY KEY (id)` + +Índices adicionales: `identidad_proveedor_sujeto`, `identidad_usuario`. + +### `limite` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ----------- | --------------- | :--: | ----------------- | +| `clave` | `text` | no | — | +| `intentos` | `integer` | no | — | +| `expira_en` | `bigint` | no | — | + +Claves y restricciones: + +- **PK limite_pkey:** `PRIMARY KEY (clave)` + +Índices adicionales: `limite_expira`. + +### `plan_suscripcion` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| --------------------------- | -------------------------- | :--: | ------------------ | +| `id` | `text` | no | — | +| `codigo` | `text` | no | — | +| `nombre` | `text` | no | — | +| `descripcion` | `text` | sí | — | +| `nivel` | `integer` | no | — | +| `precio_centimos` | `integer` | no | — | +| `moneda` | `text` | no | `'eur'::text` | +| `periodicidad` | `text` | no | — | +| `intervalos` | `integer` | no | `1` | +| `referencia_precio_externa` | `text` | sí | — | +| `prestaciones` | `jsonb` | no | `'[]'::jsonb` | +| `estado` | `text` | no | `'borrador'::text` | +| `creado_en` | `timestamp with time zone` | no | `now()` | +| `actualizado_en` | `timestamp with time zone` | no | `now()` | +| `incluye_publicidad` | `boolean` | no | `false` | +| `calidad_escucha` | `text` | sí | `'media'::text` | +| `descargas_incluidas` | `integer` | no | `0` | +| `calidad_descarga` | `text` | sí | — | +| `acceso_versiones` | `boolean` | no | `false` | +| `ambito` | `text` | no | `'musica'::text` | +| `acceso_contenido_taller` | `boolean` | no | `false` | +| `descarga_material_taller` | `boolean` | no | `false` | +| `acceso_jukebox` | `boolean` | no | `false` | + +Claves y restricciones: + +- **CHECK plan_suscripcion_ambito_conocido:** `CHECK (ambito = ANY (ARRAY['musica'::text, 'taller_letras'::text]))` +- **CHECK plan_suscripcion_calidad_descarga_conocida:** `CHECK (calidad_descarga IS NULL OR (calidad_descarga = ANY (ARRAY['media'::text, 'alta'::text])))` +- **CHECK plan_suscripcion_calidad_escucha_conocida:** `CHECK (calidad_escucha IS NULL OR (calidad_escucha = ANY (ARRAY['media'::text, 'alta'::text])))` +- **CHECK plan_suscripcion_calidad_solo_con_descargas:** `CHECK (descargas_incluidas > 0 OR calidad_descarga IS NULL)` +- **CHECK plan_suscripcion_capacidades_segun_ambito:** `CHECK (ambito = 'musica'::text AND calidad_escucha IS NOT NULL AND NOT acceso_contenido_taller AND NOT descarga_material_taller OR ambito = 'taller_letras'::text AND calidad_escucha IS NULL AND NOT incluye_publicidad AND descargas_incluidas = 0 AND calidad_descarga IS NULL AND NOT acceso_versiones AND NOT acceso_jukebox)` +- **CHECK plan_suscripcion_codigo_no_vacio:** `CHECK (btrim(codigo) <> ''::text)` +- **CHECK plan_suscripcion_descargas_no_negativas:** `CHECK (descargas_incluidas >= 0)` +- **CHECK plan_suscripcion_estado_conocido:** `CHECK (estado = ANY (ARRAY['borrador'::text, 'disponible'::text, 'retirado'::text]))` +- **CHECK plan_suscripcion_intervalos_positivos:** `CHECK (intervalos > 0)` +- **CHECK plan_suscripcion_moneda_iso:** `CHECK (moneda ~ '^[a-z]{3}$'::text)` +- **CHECK plan_suscripcion_nivel_positivo:** `CHECK (nivel > 0)` +- **CHECK plan_suscripcion_nombre_no_vacio:** `CHECK (btrim(nombre) <> ''::text)` +- **CHECK plan_suscripcion_periodicidad_conocida:** `CHECK (periodicidad = ANY (ARRAY['dia'::text, 'semana'::text, 'mes'::text, 'ano'::text]))` +- **CHECK plan_suscripcion_precio_no_negativo:** `CHECK (precio_centimos >= 0)` +- **CHECK plan_suscripcion_prestaciones_array:** `CHECK (jsonb_typeof(prestaciones) = 'array'::text)` +- **PK plan_suscripcion_pkey:** `PRIMARY KEY (id)` +- **UNIQUE plan_suscripcion_codigo_unique:** `UNIQUE (codigo)` +- **UNIQUE plan_suscripcion_referencia_precio_externa_unique:** `UNIQUE (referencia_precio_externa)` + +Índices adicionales: `plan_suscripcion_estado_nivel`. + +### `sesion` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | -------------------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `usuario_id` | `text` | no | — | +| `expira_en` | `timestamp with time zone` | no | — | +| `creada_en` | `timestamp with time zone` | no | `now()` | + +Claves y restricciones: + +- **FK sesion_usuario_id_usuario_id_fk:** `FOREIGN KEY (usuario_id) REFERENCES usuario(id) ON DELETE CASCADE` +- **PK sesion_pkey:** `PRIMARY KEY (id)` + +Índices adicionales: `sesion_usuario`. + +### `suscripcion` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| -------------------- | -------------------------- | :--: | ------------------- | +| `id` | `text` | no | — | +| `usuario_id` | `text` | no | — | +| `estado` | `text` | no | `'pendiente'::text` | +| `inicia_en` | `timestamp with time zone` | no | `now()` | +| `termina_en` | `timestamp with time zone` | sí | — | +| `proveedor` | `text` | sí | — | +| `referencia_externa` | `text` | sí | — | +| `creada_en` | `timestamp with time zone` | no | `now()` | +| `actualizada_en` | `timestamp with time zone` | no | `now()` | +| `plan_id` | `text` | sí | — | + +Claves y restricciones: + +- **CHECK suscripcion_estado_conocido:** `CHECK (estado = ANY (ARRAY['pendiente'::text, 'activa'::text, 'pausada'::text, 'cancelada'::text, 'vencida'::text]))` +- **CHECK suscripcion_periodo_valido:** `CHECK (termina_en IS NULL OR termina_en > inicia_en)` +- **CHECK suscripcion_referencia_completa:** `CHECK ((proveedor IS NULL) = (referencia_externa IS NULL))` +- **FK suscripcion_plan_id_plan_suscripcion_id_fk:** `FOREIGN KEY (plan_id) REFERENCES plan_suscripcion(id) ON DELETE RESTRICT` +- **FK suscripcion_usuario_id_usuario_id_fk:** `FOREIGN KEY (usuario_id) REFERENCES usuario(id) ON DELETE CASCADE` +- **PK suscripcion_pkey:** `PRIMARY KEY (id)` + +Índices adicionales: `suscripcion_proveedor_referencia`, `suscripcion_usuario_estado`. + +### `usuario` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------------ | -------------------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `email` | `text` | no | — | +| `nombre` | `text` | sí | — | +| `creado_en` | `timestamp with time zone` | no | `now()` | +| `ultimo_acceso_en` | `timestamp with time zone` | sí | — | +| `rol` | `text` | no | `'persona'::text` | + +Claves y restricciones: + +- **CHECK usuario_rol_conocido:** `CHECK (rol = ANY (ARRAY['persona'::text, 'admin'::text]))` +- **PK usuario_pkey:** `PRIMARY KEY (id)` +- **UNIQUE usuario_email_unique:** `UNIQUE (email)` + +## Interacción y escucha + +### `acceso_cancion_usuario` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| -------------- | -------------------------- | :--: | ----------------- | +| `usuario_id` | `text` | no | — | +| `cancion_id` | `text` | no | — | +| `concedido_en` | `timestamp with time zone` | no | `now()` | + +Claves y restricciones: + +- **FK acceso_cancion_usuario_cancion_id_cancion_id_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE CASCADE` +- **FK acceso_cancion_usuario_usuario_id_usuario_id_fk:** `FOREIGN KEY (usuario_id) REFERENCES usuario(id) ON DELETE CASCADE` +- **PK acceso_cancion_usuario_usuario_id_cancion_id_pk:** `PRIMARY KEY (usuario_id, cancion_id)` + +Índices adicionales: `acceso_cancion_usuario_cancion`. + +### `favorito` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | -------------------------- | :--: | ----------------- | +| `usuario_id` | `text` | no | — | +| `marcado_en` | `timestamp with time zone` | no | `now()` | +| `cancion_id` | `text` | no | — | + +Claves y restricciones: + +- **FK favorito_cancion_id_cancion_id_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE CASCADE` +- **FK favorito_usuario_id_usuario_id_fk:** `FOREIGN KEY (usuario_id) REFERENCES usuario(id) ON DELETE CASCADE` +- **PK favorito_usuario_id_cancion_id_pk:** `PRIMARY KEY (usuario_id, cancion_id)` + +Índices adicionales: `idx_favorito_usuario`. + +### `lista_reproduccion` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ---------------- | -------------------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `usuario_id` | `text` | no | — | +| `nombre` | `text` | no | — | +| `creada_en` | `timestamp with time zone` | no | `now()` | +| `actualizada_en` | `timestamp with time zone` | no | `now()` | + +Claves y restricciones: + +- **CHECK lista_reproduccion_nombre_valido:** `CHECK (NULLIF(btrim(nombre), ''::text) IS NOT NULL AND char_length(btrim(nombre)) <= 80)` +- **FK lista_reproduccion_usuario_id_usuario_id_fk:** `FOREIGN KEY (usuario_id) REFERENCES usuario(id) ON DELETE CASCADE` +- **PK lista_reproduccion_pkey:** `PRIMARY KEY (id)` + +Índices adicionales: `lista_reproduccion_nombre_usuario`, `lista_reproduccion_usuario`. + +### `lista_reproduccion_cancion` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | -------------------------- | :--: | ----------------- | +| `lista_id` | `text` | no | — | +| `cancion_id` | `text` | no | — | +| `orden` | `integer` | no | — | +| `anadida_en` | `timestamp with time zone` | no | `now()` | + +Claves y restricciones: + +- **CHECK lista_reproduccion_orden_positivo:** `CHECK (orden >= 0)` +- **FK lista_reproduccion_cancion_cancion_id_cancion_id_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE CASCADE` +- **FK lista_reproduccion_cancion_lista_id_lista_reproduccion_id_fk:** `FOREIGN KEY (lista_id) REFERENCES lista_reproduccion(id) ON DELETE CASCADE` +- **PK lista_reproduccion_cancion_lista_id_cancion_id_pk:** `PRIMARY KEY (lista_id, cancion_id)` + +Índices adicionales: `lista_reproduccion_cancion_tema`, `lista_reproduccion_orden_unico`. + +### `reproduccion` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | -------------------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `usuario_id` | `text` | sí | — | +| `segundos` | `integer` | no | `0` | +| `dia` | `text` | no | — | +| `creado_en` | `timestamp with time zone` | no | `now()` | +| `cancion_id` | `text` | no | — | + +Claves y restricciones: + +- **FK reproduccion_cancion_id_cancion_id_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE CASCADE` +- **FK reproduccion_usuario_id_usuario_id_fk:** `FOREIGN KEY (usuario_id) REFERENCES usuario(id) ON DELETE SET NULL` +- **PK reproduccion_pkey:** `PRIMARY KEY (id)` + +Índices adicionales: `reproduccion_cancion`, `reproduccion_dia`, `reproduccion_usuario`. + +### `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. + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ---------------- | -------------------------- | :--: | ----------------- | +| `usuario_id` | `text` | no | — | +| `value` | `integer` | sí | — | +| `date` | `timestamp with time zone` | no | `now()` | +| `actualizada_en` | `timestamp with time zone` | no | `now()` | +| `cancion_id` | `text` | no | — | +| `like` | `boolean` | no | `false` | + +Claves y restricciones: + +- **CHECK valoracion_de_uno_a_cinco:** `CHECK (value >= 1 AND value <= 5)` +- **FK valoracion_cancion_id_cancion_id_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE CASCADE` +- **FK valoracion_usuario_id_usuario_id_fk:** `FOREIGN KEY (usuario_id) REFERENCES usuario(id) ON DELETE CASCADE` +- **PK valoracion_usuario_id_cancion_id_pk:** `PRIMARY KEY (usuario_id, cancion_id)` + +Índices adicionales: `idx_valoracion_cancion`. + +## Comercio + +### `carrito` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ---------------- | -------------------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `usuario_id` | `text` | sí | — | +| `creado_en` | `timestamp with time zone` | no | `now()` | +| `actualizado_en` | `timestamp with time zone` | no | `now()` | + +Claves y restricciones: + +- **FK carrito_usuario_id_usuario_id_fk:** `FOREIGN KEY (usuario_id) REFERENCES usuario(id) ON DELETE CASCADE` +- **PK carrito_pkey:** `PRIMARY KEY (id)` + +Índices adicionales: `carrito_usuario_unico`. + +### `carrito_item` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | -------------------------- | :--: | ----------------- | +| `carrito_id` | `text` | no | — | +| `anadido_en` | `timestamp with time zone` | no | `now()` | +| `cancion_id` | `text` | no | — | + +Claves y restricciones: + +- **FK carrito_item_cancion_id_cancion_id_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE CASCADE` +- **FK carrito_item_carrito_id_carrito_id_fk:** `FOREIGN KEY (carrito_id) REFERENCES carrito(id) ON DELETE CASCADE` +- **PK carrito_item_carrito_id_cancion_id_pk:** `PRIMARY KEY (carrito_id, cancion_id)` + +### `carrito_producto` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------- | -------------------------- | :--: | ----------------- | +| `carrito_id` | `text` | no | — | +| `producto_id` | `text` | no | — | +| `cantidad` | `integer` | no | `1` | +| `anadido_en` | `timestamp with time zone` | no | `now()` | + +Claves y restricciones: + +- **CHECK carrito_producto_cantidad_positiva:** `CHECK (cantidad > 0)` +- **FK carrito_producto_carrito_id_carrito_id_fk:** `FOREIGN KEY (carrito_id) REFERENCES carrito(id) ON DELETE CASCADE` +- **FK carrito_producto_producto_id_producto_id_fk:** `FOREIGN KEY (producto_id) REFERENCES producto(id) ON DELETE CASCADE` +- **PK carrito_producto_carrito_id_producto_id_pk:** `PRIMARY KEY (carrito_id, producto_id)` + +### `compra` + +Permiso materializado de descarga de una canción adquirida. + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------- | -------------------------- | :--: | ----------------- | +| `usuario_id` | `text` | no | — | +| `pedido_id` | `text` | no | — | +| `comprado_en` | `timestamp with time zone` | no | `now()` | +| `cancion_id` | `text` | no | — | + +Claves y restricciones: + +- **FK compra_cancion_id_cancion_id_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE RESTRICT` +- **FK compra_pedido_id_pedido_id_fk:** `FOREIGN KEY (pedido_id) REFERENCES pedido(id) ON DELETE CASCADE` +- **FK compra_usuario_id_usuario_id_fk:** `FOREIGN KEY (usuario_id) REFERENCES usuario(id) ON DELETE CASCADE` +- **PK compra_usuario_id_cancion_id_pk:** `PRIMARY KEY (usuario_id, cancion_id)` + +### `intencion_pago` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ---------------- | -------------------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `usuario_id` | `text` | no | — | +| `huella` | `text` | no | — | +| `datos` | `jsonb` | no | — | +| `sesion_pago_id` | `text` | sí | — | +| `estado` | `text` | no | `'abierta'::text` | +| `creado_en` | `timestamp with time zone` | no | `now()` | + +Claves y restricciones: + +- **FK intencion_pago_usuario_id_usuario_id_fk:** `FOREIGN KEY (usuario_id) REFERENCES usuario(id) ON DELETE CASCADE` +- **PK intencion_pago_pkey:** `PRIMARY KEY (id)` +- **UNIQUE intencion_pago_sesion_pago_id_unique:** `UNIQUE (sesion_pago_id)` + +Índices adicionales: `intencion_pago_abierta`. + +### `pedido` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ---------------- | -------------------------- | :--: | ------------------- | +| `id` | `text` | no | — | +| `usuario_id` | `text` | no | — | +| `sesion_pago_id` | `text` | no | — | +| `estado` | `text` | no | `'pendiente'::text` | +| `packs` | `integer` | no | — | +| `total_centimos` | `integer` | no | — | +| `moneda` | `text` | no | `'eur'::text` | +| `creado_en` | `timestamp with time zone` | no | `now()` | +| `pagado_en` | `timestamp with time zone` | sí | — | + +Claves y restricciones: + +- **FK pedido_usuario_id_usuario_id_fk:** `FOREIGN KEY (usuario_id) REFERENCES usuario(id) ON DELETE CASCADE` +- **PK pedido_pkey:** `PRIMARY KEY (id)` +- **UNIQUE pedido_sesion_pago_id_unique:** `UNIQUE (sesion_pago_id)` + +Índices adicionales: `pedido_usuario`. + +### `pedido_item` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | --------------- | :--: | ----------------- | +| `pedido_id` | `text` | no | — | +| `titulo` | `text` | no | — | +| `cancion_id` | `text` | no | — | + +Claves y restricciones: + +- **FK pedido_item_cancion_id_cancion_id_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE RESTRICT` +- **FK pedido_item_pedido_id_pedido_id_fk:** `FOREIGN KEY (pedido_id) REFERENCES pedido(id) ON DELETE CASCADE` +- **PK pedido_item_pedido_id_cancion_id_pk:** `PRIMARY KEY (pedido_id, cancion_id)` + +### `pedido_linea` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| -------------------------- | --------------- | :--: | ----------------- | +| `pedido_id` | `text` | no | — | +| `codigo` | `text` | no | — | +| `producto_id` | `text` | sí | — | +| `tipo` | `text` | no | — | +| `nombre` | `text` | no | — | +| `descripcion` | `text` | sí | — | +| `cantidad` | `integer` | no | — | +| `precio_unitario_centimos` | `integer` | no | — | +| `total_centimos` | `integer` | no | — | +| `moneda` | `text` | no | — | +| `modalidad` | `text` | no | — | +| `datos` | `jsonb` | no | `'{}'::jsonb` | + +Claves y restricciones: + +- **CHECK pedido_linea_cantidad_positiva:** `CHECK (cantidad > 0)` +- **CHECK pedido_linea_datos_objeto:** `CHECK (jsonb_typeof(datos) = 'object'::text)` +- **CHECK pedido_linea_importes_validos:** `CHECK (precio_unitario_centimos >= 0 AND total_centimos = (precio_unitario_centimos * cantidad))` +- **CHECK pedido_linea_modalidad_conocida:** `CHECK (modalidad = ANY (ARRAY['pago_unico'::text, 'suscripcion'::text]))` +- **CHECK pedido_linea_moneda_iso:** `CHECK (moneda ~ '^[a-z]{3}$'::text)` +- **CHECK pedido_linea_tipo_conocido:** `CHECK (tipo = ANY (ARRAY['pack_canciones'::text, 'libro'::text, 'partitura'::text, 'midi'::text, 'stems'::text, 'servicio'::text, 'suscripcion'::text]))` +- **FK pedido_linea_pedido_id_pedido_id_fk:** `FOREIGN KEY (pedido_id) REFERENCES pedido(id) ON DELETE CASCADE` +- **FK pedido_linea_producto_id_producto_id_fk:** `FOREIGN KEY (producto_id) REFERENCES producto(id) ON DELETE SET NULL` +- **PK pedido_linea_pedido_id_codigo_pk:** `PRIMARY KEY (pedido_id, codigo)` + +Índices adicionales: `pedido_linea_tipo`. + +### `producto` + +Oferta comercial genérica; sus datos específicos se conservan en JSON. + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ----------------- | -------------------------- | :--: | ------------------ | +| `id` | `text` | no | — | +| `codigo` | `text` | no | — | +| `tipo` | `text` | no | — | +| `nombre` | `text` | no | — | +| `descripcion` | `text` | sí | — | +| `estado` | `text` | no | `'borrador'::text` | +| `precio_centimos` | `integer` | no | — | +| `moneda` | `text` | no | `'eur'::text` | +| `metadatos` | `jsonb` | no | `'{}'::jsonb` | +| `creado_en` | `timestamp with time zone` | no | `now()` | +| `actualizado_en` | `timestamp with time zone` | no | `now()` | + +Claves y restricciones: + +- **CHECK producto_codigo_no_vacio:** `CHECK (btrim(codigo) <> ''::text)` +- **CHECK producto_estado_conocido:** `CHECK (estado = ANY (ARRAY['borrador'::text, 'disponible'::text, 'retirado'::text]))` +- **CHECK producto_metadatos_objeto:** `CHECK (jsonb_typeof(metadatos) = 'object'::text)` +- **CHECK producto_moneda_iso:** `CHECK (moneda ~ '^[a-z]{3}$'::text)` +- **CHECK producto_nombre_no_vacio:** `CHECK (btrim(nombre) <> ''::text)` +- **CHECK producto_precio_no_negativo:** `CHECK (precio_centimos >= 0)` +- **CHECK producto_tipo_conocido:** `CHECK (tipo = ANY (ARRAY['libro'::text, 'partitura'::text, 'midi'::text, 'stems'::text, 'servicio'::text]))` +- **PK producto_pkey:** `PRIMARY KEY (id)` +- **UNIQUE producto_codigo_unique:** `UNIQUE (codigo)` + +Índices adicionales: `producto_tipo_estado`. + +## Facturación + +### `contador_factura` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ---------------- | -------------------------- | :--: | ----------------- | +| `serie` | `text` | no | — | +| `ultimo_numero` | `integer` | no | `0` | +| `actualizado_en` | `timestamp with time zone` | no | `now()` | + +Claves y restricciones: + +- **PK contador_factura_pkey:** `PRIMARY KEY (serie)` + +### `datos_facturacion` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ---------------- | -------------------------- | :--: | -------------------- | +| `id` | `text` | no | — | +| `usuario_id` | `text` | no | — | +| `tipo` | `text` | no | `'particular'::text` | +| `nombre` | `text` | no | — | +| `nif` | `text` | sí | — | +| `direccion` | `text` | sí | — | +| `cp` | `text` | sí | — | +| `ciudad` | `text` | sí | — | +| `provincia` | `text` | sí | — | +| `pais_codigo` | `character(2)` | no | — | +| `predeterminada` | `boolean` | no | `false` | +| `creado_en` | `timestamp with time zone` | no | `now()` | +| `actualizado_en` | `timestamp with time zone` | sí | — | + +Claves y restricciones: + +- **FK datos_facturacion_pais_codigo_pais_codigo_fk:** `FOREIGN KEY (pais_codigo) REFERENCES pais(codigo)` +- **FK datos_facturacion_usuario_id_usuario_id_fk:** `FOREIGN KEY (usuario_id) REFERENCES usuario(id) ON DELETE CASCADE` +- **PK datos_facturacion_pkey:** `PRIMARY KEY (id)` + +Índices adicionales: `datos_facturacion_usuario`. + +### `descarga` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | -------------------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `usuario_id` | `text` | no | — | +| `bajada_en` | `timestamp with time zone` | no | `now()` | +| `ip` | `text` | sí | — | +| `agente` | `text` | sí | — | +| `cancion_id` | `text` | no | — | + +Claves y restricciones: + +- **FK descarga_cancion_id_cancion_id_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE RESTRICT` +- **FK descarga_usuario_id_usuario_id_fk:** `FOREIGN KEY (usuario_id) REFERENCES usuario(id) ON DELETE CASCADE` +- **PK descarga_pkey:** `PRIMARY KEY (id)` + +Índices adicionales: `descarga_fecha`, `descarga_usuario`. + +### `factura` + +Documento fiscal inmutable; una rectificativa referencia otra factura. + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| -------------------- | -------------------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `pedido_id` | `text` | no | — | +| `serie` | `text` | no | — | +| `numero` | `integer` | no | — | +| `tipo` | `text` | no | `'normal'::text` | +| `rectifica_a_id` | `text` | sí | — | +| `fecha` | `timestamp with time zone` | no | `now()` | +| `base_centimos` | `integer` | no | — | +| `iva_tipo` | `integer` | no | — | +| `iva_cuota_centimos` | `integer` | no | — | +| `total_centimos` | `integer` | no | — | +| `moneda` | `text` | no | `'eur'::text` | +| `datos_congelados` | `jsonb` | no | — | +| `pais_codigo` | `character(2)` | no | — | + +Claves y restricciones: + +- **CHECK factura_rectificativa_apunta:** `CHECK ((tipo = 'rectificativa'::text) = (rectifica_a_id IS NOT NULL))` +- **FK factura_pais_codigo_pais_codigo_fk:** `FOREIGN KEY (pais_codigo) REFERENCES pais(codigo)` +- **FK factura_pedido_id_pedido_id_fk:** `FOREIGN KEY (pedido_id) REFERENCES pedido(id)` +- **FK factura_rectifica_fk:** `FOREIGN KEY (rectifica_a_id) REFERENCES factura(id)` +- **PK factura_pkey:** `PRIMARY KEY (id)` + +Índices adicionales: `factura_fecha`, `factura_pedido`, `factura_serie_numero`. + +## Editorial y legal + +### `documento_legal` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| -------------------- | --------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `slug` | `text` | no | — | +| `titulo` | `text` | no | — | +| `resumen` | `text` | no | `''::text` | +| `orden` | `integer` | no | `0` | +| `version_vigente_id` | `text` | sí | — | + +Claves y restricciones: + +- **FK documento_legal_version_vigente_fk:** `FOREIGN KEY (version_vigente_id) REFERENCES version_legal(id) ON DELETE SET NULL` +- **PK documento_legal_pkey:** `PRIMARY KEY (id)` +- **UNIQUE documento_legal_slug_unique:** `UNIQUE (slug)` + +### `entrada` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ---------------- | -------------------------- | :--: | ------------------ | +| `id` | `text` | no | — | +| `slug` | `text` | no | — | +| `titulo` | `text` | no | — | +| `fecha` | `text` | no | — | +| `resumen` | `text` | no | `''::text` | +| `cuerpo` | `text` | no | `''::text` | +| `imagen_id` | `text` | sí | — | +| `estado` | `text` | no | `'borrador'::text` | +| `creado_en` | `timestamp with time zone` | no | `now()` | +| `actualizado_en` | `timestamp with time zone` | sí | — | +| `secciones` | `jsonb` | no | `'[]'::jsonb` | +| `presentacion` | `jsonb` | no | `'{}'::jsonb` | + +Claves y restricciones: + +- **CHECK entrada_presentacion_objeto:** `CHECK (jsonb_typeof(presentacion) = 'object'::text)` +- **CHECK entrada_secciones_array:** `CHECK (jsonb_typeof(secciones) = 'array'::text)` +- **FK entrada_imagen_id_medio_id_fk:** `FOREIGN KEY (imagen_id) REFERENCES medio(id) ON DELETE SET NULL` +- **PK entrada_pkey:** `PRIMARY KEY (id)` +- **UNIQUE entrada_slug_unique:** `UNIQUE (slug)` + +Índices adicionales: `entrada_fecha`. + +### `entrada_cancion` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | --------------- | :--: | ----------------- | +| `entrada_id` | `text` | no | — | +| `cancion_id` | `text` | no | — | + +Claves y restricciones: + +- **FK entrada_cancion_cancion_id_cancion_id_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE CASCADE` +- **FK entrada_cancion_entrada_id_entrada_id_fk:** `FOREIGN KEY (entrada_id) REFERENCES entrada(id) ON DELETE CASCADE` +- **PK entrada_cancion_entrada_id_cancion_id_pk:** `PRIMARY KEY (entrada_id, cancion_id)` + +### `entrada_etiqueta` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------- | --------------- | :--: | ----------------- | +| `entrada_id` | `text` | no | — | +| `etiqueta_id` | `text` | no | — | + +Claves y restricciones: + +- **FK entrada_etiqueta_entrada_id_entrada_id_fk:** `FOREIGN KEY (entrada_id) REFERENCES entrada(id) ON DELETE CASCADE` +- **FK entrada_etiqueta_etiqueta_id_etiqueta_id_fk:** `FOREIGN KEY (etiqueta_id) REFERENCES etiqueta(id) ON DELETE CASCADE` +- **PK entrada_etiqueta_entrada_id_etiqueta_id_pk:** `PRIMARY KEY (entrada_id, etiqueta_id)` + +### `pagina` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ---------------- | -------------------------- | :--: | ------------------ | +| `id` | `text` | no | — | +| `slug` | `text` | no | — | +| `titulo` | `text` | no | — | +| `descripcion` | `text` | no | `''::text` | +| `cuerpo` | `text` | no | `''::text` | +| `estado` | `text` | no | `'borrador'::text` | +| `creado_en` | `timestamp with time zone` | no | `now()` | +| `actualizado_en` | `timestamp with time zone` | sí | — | + +Claves y restricciones: + +- **PK pagina_pkey:** `PRIMARY KEY (id)` +- **UNIQUE pagina_slug_unique:** `UNIQUE (slug)` + +### `redireccion` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | -------------------------- | :--: | ----------------- | +| `desde` | `text` | no | — | +| `hacia` | `text` | no | — | +| `permanente` | `boolean` | no | `true` | +| `motivo` | `text` | sí | — | +| `creada_en` | `timestamp with time zone` | no | `now()` | + +Claves y restricciones: + +- **PK redireccion_pkey:** `PRIMARY KEY (desde)` + +Índices adicionales: `redireccion_hacia`. + +### `version_legal` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ----------------- | -------------------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `documento_id` | `text` | no | — | +| `cuerpo` | `text` | no | — | +| `vigente_desde` | `timestamp with time zone` | no | `now()` | +| `resumen_cambios` | `text` | sí | — | + +Claves y restricciones: + +- **FK version_legal_documento_id_documento_legal_id_fk:** `FOREIGN KEY (documento_id) REFERENCES documento_legal(id) ON DELETE CASCADE` +- **PK version_legal_pkey:** `PRIMARY KEY (id)` + +Índices adicionales: `version_legal_documento`. + +## Taller de letras + +### `acepcion` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | --------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `termino_id` | `text` | no | — | +| `numero` | `integer` | no | — | +| `categoria` | `text` | no | — | +| `definicion` | `text` | no | — | +| `ampliacion` | `text` | sí | — | +| `ejemplo` | `text` | sí | — | + +Claves y restricciones: + +- **FK acepcion_termino_id_termino_id_fk:** `FOREIGN KEY (termino_id) REFERENCES termino(id) ON DELETE CASCADE` +- **PK acepcion_pkey:** `PRIMARY KEY (id)` + +Índices adicionales: `acepcion_numero`. + +### `animo` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------- | --------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `codigo` | `text` | no | — | +| `nombre` | `text` | no | — | +| `gems` | `text` | sí | — | +| `valencia` | `integer` | sí | — | +| `energia` | `integer` | sí | — | +| `descripcion` | `text` | sí | — | + +Claves y restricciones: + +- **PK animo_pkey:** `PRIMARY KEY (id)` +- **UNIQUE animo_codigo_unique:** `UNIQUE (codigo)` + +### `cancion_genero` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | --------------- | :--: | ----------------- | +| `cancion_id` | `text` | no | — | +| `genero_id` | `text` | no | — | + +Claves y restricciones: + +- **FK cancion_genero_cancion_id_cancion_id_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE CASCADE` +- **FK cancion_genero_genero_id_genero_id_fk:** `FOREIGN KEY (genero_id) REFERENCES genero(id) ON DELETE CASCADE` +- **PK cancion_genero_cancion_id_genero_id_pk:** `PRIMARY KEY (cancion_id, genero_id)` + +### `capitulo` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ---------------- | -------------------------- | :--: | ------------------ | +| `id` | `text` | no | — | +| `slug` | `text` | no | — | +| `titulo` | `text` | no | — | +| `tipo` | `text` | no | — | +| `genero_id` | `text` | sí | — | +| `orden` | `integer` | no | `0` | +| `resumen` | `text` | no | `''::text` | +| `cuerpo` | `text` | no | `''::text` | +| `estado` | `text` | no | `'borrador'::text` | +| `creado_en` | `timestamp with time zone` | no | `now()` | +| `actualizado_en` | `timestamp with time zone` | sí | — | + +Claves y restricciones: + +- **FK capitulo_genero_id_genero_id_fk:** `FOREIGN KEY (genero_id) REFERENCES genero(id) ON DELETE SET NULL` +- **PK capitulo_pkey:** `PRIMARY KEY (id)` +- **UNIQUE capitulo_slug_unique:** `UNIQUE (slug)` + +Índices adicionales: `capitulo_tipo`. + +### `capitulo_acepcion` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------- | --------------- | :--: | ----------------- | +| `capitulo_id` | `text` | no | — | +| `acepcion_id` | `text` | no | — | +| `automatico` | `boolean` | no | `true` | +| `confirmado` | `boolean` | no | `false` | + +Claves y restricciones: + +- **FK capitulo_acepcion_acepcion_id_acepcion_id_fk:** `FOREIGN KEY (acepcion_id) REFERENCES acepcion(id) ON DELETE CASCADE` +- **FK capitulo_acepcion_capitulo_id_capitulo_id_fk:** `FOREIGN KEY (capitulo_id) REFERENCES capitulo(id) ON DELETE CASCADE` +- **PK capitulo_acepcion_capitulo_id_acepcion_id_pk:** `PRIMARY KEY (capitulo_id, acepcion_id)` + +### `compas` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------- | --------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `codigo` | `text` | no | — | +| `nombre` | `text` | sí | — | +| `numerador` | `integer` | no | — | +| `denominador` | `integer` | no | — | +| `nota` | `text` | sí | — | + +Claves y restricciones: + +- **PK compas_pkey:** `PRIMARY KEY (id)` +- **UNIQUE compas_codigo_unique:** `UNIQUE (codigo)` + +### `genero` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ---------------- | -------------------------- | :--: | ------------------ | +| `id` | `text` | no | — | +| `slug` | `text` | no | — | +| `nombre` | `text` | no | — | +| `resumen` | `text` | no | `''::text` | +| `origen_lugar` | `text` | sí | — | +| `origen_ano` | `integer` | sí | — | +| `tempo` | `text` | sí | — | +| `rima` | `text` | sí | — | +| `metrica_nota` | `text` | sí | — | +| `compas_nota` | `text` | sí | — | +| `rasgos` | `text[]` | sí | — | +| `estado` | `text` | no | `'borrador'::text` | +| `origen` | `text` | no | `'manual'::text` | +| `modelo_origen` | `text` | sí | — | +| `importado_en` | `timestamp with time zone` | sí | — | +| `revisado_por` | `text` | sí | — | +| `revisado_en` | `timestamp with time zone` | sí | — | +| `creado_en` | `timestamp with time zone` | no | `now()` | +| `actualizado_en` | `timestamp with time zone` | sí | — | + +Claves y restricciones: + +- **PK genero_pkey:** `PRIMARY KEY (id)` +- **UNIQUE genero_slug_unique:** `UNIQUE (slug)` + +Índices adicionales: `genero_nombre`, `genero_origen_ano`. + +### `genero_alias` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------- | --------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `genero_id` | `text` | no | — | +| `nombre` | `text` | no | — | +| `pais_codigo` | `character(2)` | sí | — | +| `nota` | `text` | sí | — | + +Claves y restricciones: + +- **FK genero_alias_genero_id_genero_id_fk:** `FOREIGN KEY (genero_id) REFERENCES genero(id) ON DELETE CASCADE` +- **FK genero_alias_pais_codigo_pais_codigo_fk:** `FOREIGN KEY (pais_codigo) REFERENCES pais(codigo)` +- **PK genero_alias_pkey:** `PRIMARY KEY (id)` + +Índices adicionales: `genero_alias_nombre`. + +### `genero_compas` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ----------- | --------------- | :--: | ----------------- | +| `genero_id` | `text` | no | — | +| `compas_id` | `text` | no | — | +| `principal` | `boolean` | no | `false` | +| `nota` | `text` | sí | — | + +Claves y restricciones: + +- **FK genero_compas_compas_id_compas_id_fk:** `FOREIGN KEY (compas_id) REFERENCES compas(id)` +- **FK genero_compas_genero_id_genero_id_fk:** `FOREIGN KEY (genero_id) REFERENCES genero(id) ON DELETE CASCADE` +- **PK genero_compas_genero_id_compas_id_pk:** `PRIMARY KEY (genero_id, compas_id)` + +### `genero_instrumento` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ---------------- | --------------- | :--: | ------------------ | +| `genero_id` | `text` | no | — | +| `instrumento_id` | `text` | no | — | +| `importancia` | `text` | no | `'habitual'::text` | +| `papel` | `text` | sí | — | +| `orden` | `integer` | no | `0` | + +Claves y restricciones: + +- **FK genero_instrumento_genero_id_genero_id_fk:** `FOREIGN KEY (genero_id) REFERENCES genero(id) ON DELETE CASCADE` +- **FK genero_instrumento_instrumento_id_instrumento_id_fk:** `FOREIGN KEY (instrumento_id) REFERENCES instrumento(id)` +- **PK genero_instrumento_genero_id_instrumento_id_pk:** `PRIMARY KEY (genero_id, instrumento_id)` + +### `genero_metro` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| -------------- | --------------- | :--: | ----------------- | +| `genero_id` | `text` | no | — | +| `metro_id` | `text` | no | — | +| `predominante` | `boolean` | no | `false` | + +Claves y restricciones: + +- **FK genero_metro_genero_id_genero_id_fk:** `FOREIGN KEY (genero_id) REFERENCES genero(id) ON DELETE CASCADE` +- **FK genero_metro_metro_id_metro_id_fk:** `FOREIGN KEY (metro_id) REFERENCES metro(id)` +- **PK genero_metro_genero_id_metro_id_pk:** `PRIMARY KEY (genero_id, metro_id)` + +### `genero_origen` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ----------- | --------------- | :--: | ----------------- | +| `genero_id` | `text` | no | — | +| `origen_id` | `text` | no | — | +| `tipo` | `text` | no | — | +| `principal` | `boolean` | no | `false` | +| `nota` | `text` | sí | — | +| `orden` | `integer` | no | `0` | + +Claves y restricciones: + +- **CHECK genero_origen_no_reflexivo:** `CHECK (genero_id <> origen_id)` +- **FK genero_origen_genero_id_genero_id_fk:** `FOREIGN KEY (genero_id) REFERENCES genero(id) ON DELETE CASCADE` +- **FK genero_origen_origen_id_genero_id_fk:** `FOREIGN KEY (origen_id) REFERENCES genero(id) ON DELETE CASCADE` +- **PK genero_origen_genero_id_origen_id_tipo_pk:** `PRIMARY KEY (genero_id, origen_id, tipo)` + +Índices adicionales: `genero_origen_origen`, `genero_origen_principal_unico`. + +Disparadores: + +- `genero_sin_ciclos_trg`: `CREATE TRIGGER genero_sin_ciclos_trg BEFORE INSERT OR UPDATE ON genero_origen FOR EACH ROW EXECUTE FUNCTION genero_sin_ciclos()` + +### `genero_pais` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------- | --------------- | :--: | ------------------ | +| `genero_id` | `text` | no | — | +| `pais_codigo` | `character(2)` | no | — | +| `relacion` | `text` | no | `'adoptado'::text` | +| `nota` | `text` | sí | — | + +Claves y restricciones: + +- **FK genero_pais_genero_id_genero_id_fk:** `FOREIGN KEY (genero_id) REFERENCES genero(id) ON DELETE CASCADE` +- **FK genero_pais_pais_codigo_pais_codigo_fk:** `FOREIGN KEY (pais_codigo) REFERENCES pais(codigo)` +- **PK genero_pais_genero_id_pais_codigo_pk:** `PRIMARY KEY (genero_id, pais_codigo)` + +### `genero_persona` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | --------------- | :--: | ----------------- | +| `genero_id` | `text` | no | — | +| `persona_id` | `text` | no | — | +| `papel` | `text` | no | — | +| `nota` | `text` | sí | — | +| `orden` | `integer` | no | `0` | + +Claves y restricciones: + +- **FK genero_persona_genero_id_genero_id_fk:** `FOREIGN KEY (genero_id) REFERENCES genero(id) ON DELETE CASCADE` +- **FK genero_persona_persona_id_persona_id_fk:** `FOREIGN KEY (persona_id) REFERENCES persona(id)` +- **PK genero_persona_genero_id_persona_id_papel_pk:** `PRIMARY KEY (genero_id, persona_id, papel)` + +### `genero_seccion` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ----------- | --------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `genero_id` | `text` | no | — | +| `clave` | `text` | no | — | +| `titulo` | `text` | no | — | +| `cuerpo` | `text` | no | — | +| `orden` | `integer` | no | `0` | + +Claves y restricciones: + +- **FK genero_seccion_genero_id_genero_id_fk:** `FOREIGN KEY (genero_id) REFERENCES genero(id) ON DELETE CASCADE` +- **PK genero_seccion_pkey:** `PRIMARY KEY (id)` + +Índices adicionales: `genero_seccion_clave`. + +### `genero_tema` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------- | --------------- | :--: | ----------------- | +| `genero_id` | `text` | no | — | +| `etiqueta_id` | `text` | no | — | +| `orden` | `integer` | no | `0` | + +Claves y restricciones: + +- **FK genero_tema_etiqueta_id_etiqueta_id_fk:** `FOREIGN KEY (etiqueta_id) REFERENCES etiqueta(id) ON DELETE CASCADE` +- **FK genero_tema_genero_id_genero_id_fk:** `FOREIGN KEY (genero_id) REFERENCES genero(id) ON DELETE CASCADE` +- **PK genero_tema_genero_id_etiqueta_id_pk:** `PRIMARY KEY (genero_id, etiqueta_id)` + +### `instrumento` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| --------------- | -------------------------- | :--: | ------------------ | +| `id` | `text` | no | — | +| `slug` | `text` | no | — | +| `nombre` | `text` | no | — | +| `familia` | `text` | no | — | +| `subfamilia` | `text` | sí | — | +| `pais_codigo` | `character(2)` | sí | — | +| `descripcion` | `text` | sí | — | +| `afinacion` | `text` | sí | — | +| `imagen_id` | `text` | sí | — | +| `muestra_id` | `text` | sí | — | +| `estado` | `text` | no | `'borrador'::text` | +| `origen` | `text` | no | `'manual'::text` | +| `modelo_origen` | `text` | sí | — | +| `importado_en` | `timestamp with time zone` | sí | — | +| `revisado_por` | `text` | sí | — | +| `revisado_en` | `timestamp with time zone` | sí | — | + +Claves y restricciones: + +- **FK instrumento_imagen_id_medio_id_fk:** `FOREIGN KEY (imagen_id) REFERENCES medio(id) ON DELETE SET NULL` +- **FK instrumento_muestra_id_medio_id_fk:** `FOREIGN KEY (muestra_id) REFERENCES medio(id) ON DELETE SET NULL` +- **FK instrumento_pais_codigo_pais_codigo_fk:** `FOREIGN KEY (pais_codigo) REFERENCES pais(codigo)` +- **PK instrumento_pkey:** `PRIMARY KEY (id)` +- **UNIQUE instrumento_slug_unique:** `UNIQUE (slug)` + +Índices adicionales: `instrumento_familia`. + +### `metro` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | --------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `codigo` | `text` | no | — | +| `nombre` | `text` | no | — | +| `silabas` | `integer` | sí | — | +| `termino_id` | `text` | sí | — | +| `nota` | `text` | sí | — | + +Claves y restricciones: + +- **FK metro_termino_fk:** `FOREIGN KEY (termino_id) REFERENCES termino(id) ON DELETE SET NULL` +- **PK metro_pkey:** `PRIMARY KEY (id)` +- **UNIQUE metro_codigo_unique:** `UNIQUE (codigo)` + +### `modelo_ia` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| --------------- | --------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `codigo` | `text` | no | — | +| `familia` | `text` | no | — | +| `version` | `text` | no | — | +| `nombre` | `text` | no | — | +| `vigente` | `boolean` | no | `true` | +| `nota_sintaxis` | `text` | sí | — | +| `esquema` | `jsonb` | sí | — | + +Claves y restricciones: + +- **PK modelo_ia_pkey:** `PRIMARY KEY (id)` +- **UNIQUE modelo_ia_codigo_unique:** `UNIQUE (codigo)` + +Índices adicionales: `modelo_ia_familia`. + +### `obra_referencia` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| --------------- | -------------------------- | :--: | ------------------ | +| `id` | `text` | no | — | +| `slug` | `text` | no | — | +| `titulo` | `text` | no | — | +| `ano` | `integer` | sí | — | +| `genero_id` | `text` | sí | — | +| `nota` | `text` | sí | — | +| `cita` | `text` | sí | — | +| `estado` | `text` | no | `'borrador'::text` | +| `origen` | `text` | no | `'manual'::text` | +| `modelo_origen` | `text` | sí | — | +| `importado_en` | `timestamp with time zone` | sí | — | +| `revisado_por` | `text` | sí | — | +| `revisado_en` | `timestamp with time zone` | sí | — | + +Claves y restricciones: + +- **FK obra_referencia_genero_id_genero_id_fk:** `FOREIGN KEY (genero_id) REFERENCES genero(id) ON DELETE SET NULL` +- **PK obra_referencia_pkey:** `PRIMARY KEY (id)` +- **UNIQUE obra_referencia_slug_unique:** `UNIQUE (slug)` + +Índices adicionales: `obra_referencia_genero`. + +### `obra_referencia_enlace` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | --------------- | :--: | ----------------- | +| `obra_id` | `text` | no | — | +| `plataforma` | `text` | no | — | +| `url` | `text` | no | — | + +Claves y restricciones: + +- **FK obra_referencia_enlace_obra_id_obra_referencia_id_fk:** `FOREIGN KEY (obra_id) REFERENCES obra_referencia(id) ON DELETE CASCADE` +- **PK obra_referencia_enlace_obra_id_plataforma_pk:** `PRIMARY KEY (obra_id, plataforma)` + +### `obra_referencia_persona` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | --------------- | :--: | ----------------- | +| `obra_id` | `text` | no | — | +| `persona_id` | `text` | no | — | +| `papel` | `text` | no | — | +| `orden` | `integer` | no | `0` | + +Claves y restricciones: + +- **FK obra_referencia_persona_obra_id_obra_referencia_id_fk:** `FOREIGN KEY (obra_id) REFERENCES obra_referencia(id) ON DELETE CASCADE` +- **FK obra_referencia_persona_persona_id_persona_id_fk:** `FOREIGN KEY (persona_id) REFERENCES persona(id)` +- **PK obra_referencia_persona_obra_id_persona_id_papel_pk:** `PRIMARY KEY (obra_id, persona_id, papel)` + +### `prompt` + +Prompt probado para un modelo de IA, género, ánimo y, opcionalmente, una canción resultante. + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ---------------- | -------------------------- | :--: | ------------------ | +| `id` | `text` | no | — | +| `modelo_ia_id` | `text` | no | — | +| `genero_id` | `text` | sí | — | +| `animo_id` | `text` | sí | — | +| `idioma` | `text` | no | `'en'::text` | +| `contenido` | `jsonb` | no | — | +| `notas` | `text` | sí | — | +| `valoracion` | `integer` | sí | — | +| `ejemplo_id` | `text` | sí | — | +| `preferido` | `boolean` | no | `false` | +| `visibilidad` | `text` | no | `'privado'::text` | +| `estado` | `text` | no | `'borrador'::text` | +| `creado_en` | `timestamp with time zone` | no | `now()` | +| `actualizado_en` | `timestamp with time zone` | sí | — | +| `cancion_id` | `text` | sí | — | + +Claves y restricciones: + +- **FK prompt_animo_id_animo_id_fk:** `FOREIGN KEY (animo_id) REFERENCES animo(id)` +- **FK prompt_cancion_id_cancion_id_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE CASCADE` +- **FK prompt_ejemplo_id_medio_id_fk:** `FOREIGN KEY (ejemplo_id) REFERENCES medio(id) ON DELETE SET NULL` +- **FK prompt_genero_id_genero_id_fk:** `FOREIGN KEY (genero_id) REFERENCES genero(id) ON DELETE CASCADE` +- **FK prompt_modelo_ia_id_modelo_ia_id_fk:** `FOREIGN KEY (modelo_ia_id) REFERENCES modelo_ia(id)` +- **PK prompt_pkey:** `PRIMARY KEY (id)` + +Índices adicionales: `prompt_animo`, `prompt_cancion`, `prompt_genero`, `prompt_modelo`. + +### `termino` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| --------- | --------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `slug` | `text` | no | — | +| `termino` | `text` | no | — | + +Claves y restricciones: + +- **PK termino_pkey:** `PRIMARY KEY (id)` +- **UNIQUE termino_slug_unique:** `UNIQUE (slug)` +- **UNIQUE termino_termino_unique:** `UNIQUE (termino)` + +### `termino_variante` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ------------ | --------------- | :--: | ----------------- | +| `termino_id` | `text` | no | — | +| `forma` | `text` | no | — | + +Claves y restricciones: + +- **FK termino_variante_termino_id_termino_id_fk:** `FOREIGN KEY (termino_id) REFERENCES termino(id) ON DELETE CASCADE` +- **PK termino_variante_termino_id_forma_pk:** `PRIMARY KEY (termino_id, forma)` + +### `verso` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| --------------- | -------------------------- | :--: | ------------------ | +| `id` | `text` | no | — | +| `genero_id` | `text` | no | — | +| `titulo` | `text` | no | — | +| `ilustra` | `text` | no | — | +| `fuente` | `text` | no | `'ejemplo'::text` | +| `analisis` | `text` | sí | — | +| `medida` | `text` | sí | — | +| `rima` | `text` | sí | — | +| `obra_id` | `text` | sí | — | +| `cancion_id` | `text` | sí | — | +| `orden` | `integer` | no | `0` | +| `estado` | `text` | no | `'borrador'::text` | +| `origen` | `text` | no | `'manual'::text` | +| `modelo_origen` | `text` | sí | — | +| `importado_en` | `timestamp with time zone` | sí | — | +| `revisado_por` | `text` | sí | — | +| `revisado_en` | `timestamp with time zone` | sí | — | + +Claves y restricciones: + +- **CHECK verso_cita_acredita:** `CHECK (fuente <> 'cita'::text OR obra_id IS NOT NULL)` +- **CHECK verso_propio_apunta:** `CHECK (fuente <> 'propio'::text OR cancion_id IS NOT NULL)` +- **FK verso_cancion_id_cancion_id_fk:** `FOREIGN KEY (cancion_id) REFERENCES cancion(id) ON DELETE SET NULL` +- **FK verso_genero_id_genero_id_fk:** `FOREIGN KEY (genero_id) REFERENCES genero(id) ON DELETE CASCADE` +- **FK verso_obra_id_obra_referencia_id_fk:** `FOREIGN KEY (obra_id) REFERENCES obra_referencia(id) ON DELETE SET NULL` +- **PK verso_pkey:** `PRIMARY KEY (id)` + +Índices adicionales: `verso_genero`. + +### `verso_linea` + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ---------- | --------------- | :--: | ----------------- | +| `verso_id` | `text` | no | — | +| `numero` | `integer` | no | — | +| `texto` | `text` | no | — | +| `silabas` | `text[]` | sí | — | +| `tonicas` | `integer[]` | sí | — | +| `medida` | `integer` | sí | — | +| `rima` | `text` | sí | — | + +Claves y restricciones: + +- **CHECK verso_linea_medida_creible:** `CHECK (medida IS NULL OR medida >= 1 AND medida <= 20)` +- **FK verso_linea_verso_id_verso_id_fk:** `FOREIGN KEY (verso_id) REFERENCES verso(id) ON DELETE CASCADE` +- **PK verso_linea_verso_id_numero_pk:** `PRIMARY KEY (verso_id, numero)` + +## Jukebox + +### `jukebox` + +Programación independiente y versionada de una cuenta. + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ---------------- | -------------------------- | :--: | ----------------- | +| `id` | `text` | no | — | +| `usuario_id` | `text` | no | — | +| `numero` | `integer` | no | — | +| `zona_horaria` | `text` | no | — | +| `franjas` | `jsonb` | no | — | +| `revision` | `integer` | no | `1` | +| `actualizada_en` | `timestamp with time zone` | no | `now()` | +| `nombre` | `text` | no | `''::text` | +| `descripcion` | `text` | no | `''::text` | + +Claves y restricciones: + +- **CHECK jukebox_franjas_array:** `CHECK (jsonb_typeof(franjas) = 'array'::text)` +- **CHECK jukebox_revision_positiva:** `CHECK (revision > 0)` +- **FK jukebox_usuario_id_fkey:** `FOREIGN KEY (usuario_id) REFERENCES usuario(id) ON DELETE CASCADE` +- **PK jukebox_pkey:** `PRIMARY KEY (id)` + +Índices adicionales: `jukebox_usuario_numero`. + +### `programacion_jukebox` + +Programación histórica por cuenta, conservada por compatibilidad con el modelo anterior de un único Jukebox. + +| Columna | Tipo PostgreSQL | Nulo | Valor por defecto | +| ---------------- | -------------------------- | :--: | ----------------------- | +| `usuario_id` | `text` | no | — | +| `zona_horaria` | `text` | no | `'Europe/Madrid'::text` | +| `franjas` | `jsonb` | no | `'[]'::jsonb` | +| `revision` | `integer` | no | `1` | +| `actualizada_en` | `timestamp with time zone` | no | `CURRENT_TIMESTAMP` | + +Claves y restricciones: + +- **CHECK programacion_jukebox_franjas_array:** `CHECK (jsonb_typeof(franjas) = 'array'::text)` +- **CHECK programacion_jukebox_revision_positiva:** `CHECK (revision > 0)` +- **FK programacion_jukebox_usuario_id_fkey:** `FOREIGN KEY (usuario_id) REFERENCES usuario(id) ON DELETE CASCADE` +- **PK programacion_jukebox_pkey:** `PRIMARY KEY (usuario_id)` + +## Vistas + +### `cancion_principal` + +| Columna | Tipo PostgreSQL | Nulo | +| --------------------- | -------------------------- | :--: | +| `id` | `text` | sí | +| `slug` | `text` | sí | +| `titulo` | `text` | sí | +| `version_de_id` | `text` | sí | +| `version_nombre` | `text` | sí | +| `version_orden` | `integer` | sí | +| `estilo_id` | `text` | sí | +| `album_id` | `text` | sí | +| `numero` | `integer` | sí | +| `duracion_s` | `integer` | sí | +| `ano` | `integer` | sí | +| `letra` | `text` | sí | +| `idioma` | `text` | sí | +| `inspiracion` | `text` | sí | +| `tiene_letra` | `boolean` | sí | +| `destacada` | `boolean` | sí | +| `portada_id` | `text` | sí | +| `ala_venta` | `boolean` | sí | +| `estado` | `text` | sí | +| `creado_en` | `timestamp with time zone` | sí | +| `actualizado_en` | `timestamp with time zone` | sí | +| `es_version` | `boolean` | sí | +| `padre_no_es_version` | `boolean` | sí | +| `version_nota` | `text` | sí | +| `codigo` | `text` | sí | +| `version_codigo` | `text` | sí | +| `formacion_vocal` | `formacion_vocal` | sí | +| `acceso` | `acceso_cancion` | sí | + +Definición: + +```sql + SELECT id, + slug, + titulo, + version_de_id, + version_nombre, + version_orden, + estilo_id, + album_id, + numero, + duracion_s, + ano, + letra, + idioma, + inspiracion, + tiene_letra, + destacada, + portada_id, + ala_venta, + estado, + creado_en, + actualizado_en, + es_version, + padre_no_es_version, + version_nota, + codigo, + version_codigo, + formacion_vocal, + acceso + FROM cancion + WHERE version_de_id IS NULL; +``` + +## Enumeraciones PostgreSQL + +- `acceso_cancion`: `ninguna`, `publico`, `exclusiva`, `privada`. +- `formacion_vocal`: `hombre`, `mujer`, `dueto-femenino`, `dueto-masculino`, `dueto-mixto`, `multiple-mixto`, `multiple-femenino`, `multiple-masculino`. + +Los demás vocabularios cerrados se almacenan como `text` y aparecen en las restricciones `CHECK` de cada tabla. + +## Funciones de base de datos + +| Función | Argumentos | Resultado | Lenguaje | +| --------------------------------- | -------------------- | --------- | --------- | +| `autoria_suma_cien` | — | `trigger` | `plpgsql` | +| `comprobar_grabacion_publicable` | `identificador text` | `void` | `plpgsql` | +| `genero_sin_ciclos` | — | `trigger` | `plpgsql` | +| `guardar_invariantes_publicacion` | — | `trigger` | `plpgsql` | + +Las funciones asociadas a disparadores se localizan además bajo la tabla afectada, en el apartado “Disparadores”. + +## Secuencias + +| Secuencia | Tipo | Inicio | Incremento | Ciclo | +| ---------------------------- | -------- | -----: | ---------: | :---: | +| `artista_codigo_seq` | `bigint` | 1 | 1 | no | +| `artista_virtual_codigo_seq` | `bigint` | 1 | 1 | no | +| `cancion_codigo_seq` | `bigint` | 1 | 1 | no | + +## 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. diff --git a/drizzle/0040_me_gusta.sql b/drizzle/0040_me_gusta.sql new file mode 100644 index 0000000..c352d7e --- /dev/null +++ b/drizzle/0040_me_gusta.sql @@ -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"); diff --git a/drizzle/0041_interaccion_unificada.sql b/drizzle/0041_interaccion_unificada.sql new file mode 100644 index 0000000..a1b3fa5 --- /dev/null +++ b/drizzle/0041_interaccion_unificada.sql @@ -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"; diff --git a/drizzle/0042_interpretes_cupido_sin_flechas.sql b/drizzle/0042_interpretes_cupido_sin_flechas.sql new file mode 100644 index 0000000..dcd7184 --- /dev/null +++ b/drizzle/0042_interpretes_cupido_sin_flechas.sql @@ -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; diff --git a/drizzle/0043_seguir_artistas.sql b/drizzle/0043_seguir_artistas.sql new file mode 100644 index 0000000..15d5cee --- /dev/null +++ b/drizzle/0043_seguir_artistas.sql @@ -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"); diff --git a/drizzle/0044_heroes_panoramicos_artistas.sql b/drizzle/0044_heroes_panoramicos_artistas.sql new file mode 100644 index 0000000..5808b52 --- /dev/null +++ b/drizzle/0044_heroes_panoramicos_artistas.sql @@ -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'; diff --git a/drizzle/0045_metadatos_retratos_artistas.sql b/drizzle/0045_metadatos_retratos_artistas.sql new file mode 100644 index 0000000..91dfc41 --- /dev/null +++ b/drizzle/0045_metadatos_retratos_artistas.sql @@ -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'; diff --git a/e2e/blog.e2e.ts b/e2e/blog.e2e.ts index 457d3ba..524da0d 100644 --- a/e2e/blog.e2e.ts +++ b/e2e/blog.e2e.ts @@ -229,66 +229,33 @@ test.describe('Blog', () => { }); }); -test.describe('Valoraciones', () => { - test('quien no ha entrado ve solo las estrellas y la puntuación', async ({ page }) => { +test.describe('Me gusta', () => { + test('muestra el pulgar y el contador, sin estrellas', async ({ page }) => { await page.goto('/canciones/prometiste'); - - const bloque = page.getByRole('region', { name: 'Valoración de la canción' }); - await expect(bloque).toBeVisible(); + const bloque = page.locator('header.ficha .me-gusta'); await expect( - bloque.getByRole('link', { name: 'Inicia sesión para valorar esta canción' }) + bloque.getByRole('link', { name: /Me gusta: \d+ likes?\. Inicia sesión/ }) ).toBeVisible(); - await expect(bloque.locator('.estrellas span')).toHaveCount(5); - await expect(bloque.locator('.media')).toBeVisible(); - await expect(bloque).not.toContainText(/valoración|de 5|votos?|quitar mi voto/i); - // Sin sesión no se puede votar: no hay estrellas que pulsar. - await expect(bloque.getByRole('radio')).toHaveCount(0); + await expect(bloque.locator('.contador')).toHaveText(/^\d+$/); + await expect(page.locator('.estrellas')).toHaveCount(0); }); - - test('las estrellas vuelven al tema tras entrar', async ({ page }) => { + test('conserva la canción como destino al entrar', async ({ page }) => { await page.goto('/canciones/prometiste'); - - await expect( - page - .getByRole('region', { name: 'Valoración de la canción' }) - .getByRole('link', { name: 'Inicia sesión para valorar esta canción' }) - ).toHaveAttribute('href', /volverA=%2Fcanciones%2Fprometiste/); - }); - - test('un POST sin origen lo para el CSRF, antes de llegar a la acción', async ({ request }) => { - const respuesta = await request.post('/canciones/prometiste?/valorar', { - form: { puntuacion: '5' } - }); - - expect(respuesta.status()).toBe(403); + await expect(page.locator('header.ficha .me-gusta a')).toHaveAttribute( + 'href', + /volverA=%2Fcanciones%2Fprometiste/ + ); }); - - test('sin sesión, la acción de valorar se rechaza', async ({ request, baseURL }) => { - // Con el origen correcto la petición llega a la acción, así que lo que se - // comprueba aquí es su guardia y no la del framework. La interfaz no - // enseña estrellas sin sesión, pero el formulario se puede enviar a mano. - // - // La respuesta es el resultado de la acción en JSON, no la página: el - // código de `fail` viaja dentro, y el de la respuesta HTTP es 200. - const respuesta = await request.post('/canciones/prometiste?/valorar', { - headers: { origin: baseURL ?? '' }, - form: { puntuacion: '5' } - }); - - const resultado = JSON.parse(await respuesta.text()); - expect(resultado.type).toBe('failure'); - expect(resultado.status).toBe(401); + test('rechaza un POST sin origen', async ({ request }) => { + const r = await request.post('/canciones/prometiste?/meGusta', { form: { activo: '1' } }); + expect(r.status()).toBe(403); }); - - test('una puntuación fuera de rango tampoco pasa', async ({ request, baseURL }) => { - // Sin sesión gana la guardia de la cuenta; que el rango se comprueba está - // cubierto aparte, en las pruebas de `aPuntuacion`. - const respuesta = await request.post('/canciones/prometiste?/valorar', { - headers: { origin: baseURL ?? '' }, - form: { puntuacion: '9' } + test('rechaza likes sin sesión', async ({ request, baseURL }) => { + const r = await request.post('/canciones/prometiste?/meGusta', { + headers: { origin: baseURL ?? '', accept: 'application/json', 'x-sveltekit-action': 'true' }, + form: { activo: '1' } }); - - expect(JSON.parse(await respuesta.text()).type).toBe('failure'); + expect(JSON.parse(await r.text())).toMatchObject({ type: 'failure', status: 401 }); }); }); diff --git a/e2e/contenido.e2e.ts b/e2e/contenido.e2e.ts index 7ccfd3a..dcb87c3 100644 --- a/e2e/contenido.e2e.ts +++ b/e2e/contenido.e2e.ts @@ -110,13 +110,15 @@ test.describe('Tarjetas de canción', () => { await expect(page).toHaveURL(/\/musica\/baladas-romanticas$/); }); - test('la tarjeta muestra la nota de valoración', async ({ page }) => { + test('la tarjeta muestra el número de likes', async ({ page }) => { await page.goto('/musica/baladas-romanticas'); const tarjeta = page .locator('article') .filter({ has: page.locator('a[href="/canciones/prometiste"]') }); - await expect(tarjeta.getByLabel('Sin valoraciones')).toContainText('Sin valorar'); + await expect( + tarjeta.getByRole('link', { name: /Me gusta: \d+ likes?\. Inicia sesión/ }) + ).toContainText(/^\d+$/); }); }); diff --git a/e2e/jukebox-controles.e2e.ts b/e2e/jukebox-controles.e2e.ts new file mode 100644 index 0000000..f416fd4 --- /dev/null +++ b/e2e/jukebox-controles.e2e.ts @@ -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(); + }); +}); diff --git a/e2e/jukebox-etiquetas.e2e.ts b/e2e/jukebox-etiquetas.e2e.ts new file mode 100644 index 0000000..8cab5ad --- /dev/null +++ b/e2e/jukebox-etiquetas.e2e.ts @@ -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(); + }); +}); diff --git a/e2e/jukebox-programacion.e2e.ts b/e2e/jukebox-programacion.e2e.ts index 30b717d..b3aae7f 100644 --- a/e2e/jukebox-programacion.e2e.ts +++ b/e2e/jukebox-programacion.e2e.ts @@ -117,7 +117,7 @@ test.describe('Programación autenticada del Juke-box', () => { await expect(page.locator('.horarios')).toContainText('Todo el catálogo'); await expect(page.locator('.horarios')).toContainText('Aleatoria'); await page.getByRole('link', { name: 'Abrir reproductor de Mañanas', exact: true }).click(); - await page.getByRole('button', { name: 'Play JukeBox', exact: true }).click(); + await page.getByRole('button', { name: 'Elegir Jukebox', exact: true }).click(); await page.getByRole('button', { name: 'Reproducir Mañanas', exact: true }).click(); await expect(page.locator('.estado-jukebox')).toContainText('Mañanas'); const alturasAcciones = await page.locator('.acciones-heroe').evaluate((fila) => @@ -143,21 +143,27 @@ test.describe('Programación autenticada del Juke-box', () => { canciones[0].titulo ); await page.getByRole('link', { name: 'Abrir reproductor de Tardes', exact: true }).click(); - await page.getByRole('button', { name: 'Play JukeBox', exact: true }).click(); + await page.getByRole('button', { name: 'Elegir Jukebox', exact: true }).click(); await page.getByRole('button', { name: 'Reproducir Tardes', exact: true }).click(); await expect(page.locator('.estado-jukebox')).toContainText('Tardes'); - await page.getByRole('button', { name: 'Detener Jukebox', exact: true }).click(); + await page + .locator('.estado-jukebox') + .getByRole('button', { name: 'Detener Jukebox', exact: true }) + .click(); await expect(page.locator('.estado-jukebox')).toContainText('Tardes'); await expect(page.locator('.estado-jukebox')).toContainText('Detenido'); expect((await page.request.get('/juke-box/' + segundo + '/programacion')).status()).toBe(200); await page.goto('/juke-box'); await page.locator('html[data-hidratado]').waitFor(); - await page.getByRole('button', { name: 'Play JukeBox', exact: true }).click(); + await page.getByRole('button', { name: 'Elegir Jukebox', exact: true }).click(); await expect(page.getByRole('list', { name: 'Tus jukebox' })).toBeVisible(); await page.getByRole('button', { name: 'Reproducir Mañanas', exact: true }).click(); await expect(page.getByRole('list', { name: 'Tus jukebox' })).not.toBeVisible(); await expect(page.locator('.estado-jukebox')).toContainText('Mañanas'); - await page.getByRole('button', { name: 'Detener Jukebox', exact: true }).click(); + await page + .locator('.estado-jukebox') + .getByRole('button', { name: 'Detener Jukebox', exact: true }) + .click(); }); test('mantiene borradores con el historial y permite cancelar un jukebox nuevo sin crearlo', async ({ page @@ -281,7 +287,7 @@ test.describe('Programación autenticada del Juke-box', () => { .locator('.entradilla span') .evaluate((s) => parseFloat(getComputedStyle(s).fontSize)) ).toBeGreaterThan(24); - await page.getByRole('button', { name: 'Play JukeBox', exact: true }).click(); + await page.getByRole('button', { name: 'Elegir Jukebox', exact: true }).click(); await expect(page.getByRole('list', { name: 'Tus jukebox' })).toContainText( 'Música para empezar el día.' ); @@ -304,7 +310,7 @@ test.describe('Programación autenticada del Juke-box', () => { ) ); await page.screenshot({ path: 'test-results-publica/jukebox-hero-100vh-claro.png' }); - await page.getByRole('button', { name: 'Play JukeBox', exact: true }).click(); + await page.getByRole('button', { name: 'Elegir Jukebox', exact: true }).click(); await page.screenshot({ path: 'test-results-publica/jukebox-desplegable-claro.png' }); await page.keyboard.press('Escape'); await page.setViewportSize({ width: 390, height: 844 }); @@ -398,7 +404,7 @@ test.describe('Programación autenticada del Juke-box', () => { await page.clock.setFixedTime(new Date('2026-09-14T08:59:50Z')); await page.goto('/juke-box?jukebox=' + primero); await page.locator('html[data-hidratado]').waitFor(); - await page.getByRole('button', { name: 'Play JukeBox', exact: true }).click(); + await page.getByRole('button', { name: 'Elegir Jukebox', exact: true }).click(); await page.getByRole('button', { name: 'Reproducir Mañanas', exact: true }).click(); await expect(page.locator('.estado-jukebox')).toContainText('Esperando próxima franja'); const respuesta = page.waitForResponse( @@ -425,7 +431,7 @@ test.describe('Programación autenticada del Juke-box', () => { await expect(page.locator('.control-jukebox')).toContainText('Esperando próxima franja'); await expect(page.getByRole('region', { name: 'Reproductor', exact: true })).toBeVisible(); await expect(page.getByRole('group', { name: 'Sonando ahora', exact: true })).toHaveCount(0); - await page.getByRole('button', { name: 'Detener', exact: true }).click(); + await page.getByRole('button', { name: 'Desactivar Jukebox', exact: true }).click(); const control = page.getByRole('group', { name: 'Jukebox', exact: true }); const barra = page.getByRole('region', { name: 'Reproductor', exact: true }); await expect(control).toContainText('Mañanas'); @@ -443,7 +449,7 @@ test.describe('Programación autenticada del Juke-box', () => { await expect .poll(() => audio.evaluate((a: HTMLAudioElement) => !a.paused && a.currentTime > 0.05)) .toBe(true); - await control.getByRole('button', { name: 'Detener', exact: true }).click(); + await control.getByRole('button', { name: 'Detener Jukebox', exact: true }).click(); await expect(control).toContainText('Detenido'); await expect(barra.getByRole('group', { name: 'Sonando ahora', exact: true })).toHaveCount(0); expect(await audio.evaluate((a: HTMLAudioElement) => a.paused)).toBe(true); @@ -473,7 +479,7 @@ test.describe('Programación autenticada del Juke-box', () => { }); await page.goto('/juke-box?jukebox=' + primero); await page.locator('html[data-hidratado]').waitFor(); - await page.getByRole('button', { name: 'Play JukeBox', exact: true }).click(); + await page.getByRole('button', { name: 'Elegir Jukebox', exact: true }).click(); await page.getByRole('button', { name: 'Reproducir Mañanas', exact: true }).click(); const barra = page.getByRole('region', { name: 'Reproductor', exact: true }); const control = barra.getByRole('group', { name: 'Jukebox', exact: true }); @@ -567,7 +573,7 @@ test.describe('Programación autenticada del Juke-box', () => { await barra.screenshot({ path: `test-results-publica/jukebox-continuar-foco-${tema}.png` }); } if (tema === 'movil') { - expect((await barra.boundingBox())!.height).toBeLessThanOrEqual(170); + expect((await barra.boundingBox())!.height).toBeLessThanOrEqual(240); await expect( barra.getByRole('button', { name: 'Tema anterior', exact: true }) ).toBeInViewport(); @@ -689,7 +695,7 @@ test.describe('Programación autenticada del Juke-box', () => { await page.setViewportSize({ width: 1440, height: 1000 }); await page.getByRole('link', { name: 'Volver al Juke-box', exact: true }).click(); await expect(page).toHaveURL('/juke-box?jukebox=' + primero); - await control.getByRole('button', { name: 'Detener', exact: true }).click(); + await control.getByRole('button', { name: 'Desactivar Jukebox', exact: true }).click(); await expect(barra).toBeVisible(); await expect(control).toContainText('Detenido'); }); diff --git a/e2e/musica.e2e.ts b/e2e/musica.e2e.ts index 8e066d9..61dbb29 100644 --- a/e2e/musica.e2e.ts +++ b/e2e/musica.e2e.ts @@ -540,28 +540,18 @@ test.describe('Página de música', () => { await expect(page.locator('.texto[lang="fr"]')).toBeVisible(); }); - test('la valoración y las acciones personales están junto al título', async ({ page }) => { + test('los likes y las acciones personales están junto al título', async ({ page }) => { await page.goto('/canciones/ton-silence-me-libere'); const cabecera = page.locator('header.ficha'); - const valoracion = cabecera.getByRole('region', { name: 'Valoración de la canción' }); - await expect(valoracion).toBeVisible(); - await expect(valoracion.getByRole('heading')).toHaveCount(0); - await expect(valoracion).not.toContainText(/valoración|de 5|votos?|quitar mi voto/i); - - const numeroValoracion = valoracion.locator('.media'); - const estrellas = await valoracion.locator('.estrellas').boundingBox(); - const puntuacion = await numeroValoracion.boundingBox(); - expect(estrellas).not.toBeNull(); - expect(puntuacion).not.toBeNull(); - const centroEstrellas = (estrellas?.y ?? 0) + (estrellas?.height ?? 0) / 2; - const centroPuntuacion = (puntuacion?.y ?? 0) + (puntuacion?.height ?? 0) / 2; - expect(Math.abs(centroEstrellas - centroPuntuacion)).toBeLessThan(2); - const familiaNumero = await numeroValoracion.evaluate( - (elemento) => getComputedStyle(elemento).fontFamily - ); - expect(familiaNumero).toContain('Archivo'); - expect(familiaNumero).not.toContain('Newsreader'); + const likes = cabecera.locator('.me-gusta'); + await expect(likes.getByRole('link', { name: /Me gusta:/ })).toBeVisible(); + await expect(likes.locator('.contador')).toHaveText(/^\d+$/); + await expect(cabecera.locator('.estrellas')).toHaveCount(0); + const familia = await likes + .locator('.contador') + .evaluate((e) => getComputedStyle(e).fontFamily); + expect(familia).toContain('Archivo'); const favorito = cabecera.getByRole('link', { name: /añadir a favoritos/i }); const lista = cabecera.getByRole('link', { name: /añadir a una lista de reproducción/i }); diff --git a/e2e/revision-publica.e2e.ts b/e2e/revision-publica.e2e.ts index 9762c12..d389c51 100644 --- a/e2e/revision-publica.e2e.ts +++ b/e2e/revision-publica.e2e.ts @@ -215,7 +215,7 @@ test('cada canción muestra sus versiones y recomendaciones en una fila horizont ).toBe(true); }); -test('favoritos, listas y valoración abren el acceso como popup sin abandonar la canción', async ({ +test('favoritos, listas y likes abren el acceso como popup sin abandonar la canción', async ({ page }) => { const ruta = '/canciones/cuando-tus-manos-me-tocan'; @@ -234,9 +234,9 @@ test('favoritos, listas y valoración abren el acceso como popup sin abandonar l 'Necesitas entrar para añadir esta canción a una lista de reproducción o crear una nueva.' }, { - nombre: /inicia sesión para valorar/i, + nombre: /Me gusta: .*Inicia sesión/i, motivo: - 'Necesitas entrar para valorar esta canción. Así cada persona puede votar una sola vez.' + 'Necesitas entrar para dar Me gusta a esta canción. Cada cuenta puede dar un like y quitarlo cuando quiera.' } ]) { await ficha.getByRole('link', { name: nombre }).click(); diff --git a/e2e/tienda.e2e.ts b/e2e/tienda.e2e.ts index a2c787f..c0cf2af 100644 --- a/e2e/tienda.e2e.ts +++ b/e2e/tienda.e2e.ts @@ -122,7 +122,7 @@ test.describe('Modal de acceso', () => { const dialogo = page.locator('dialog.acceso'); await expect(dialogo).toBeVisible(); await expect( - dialogo.getByText('Entra para acceder a tus compras, favoritos, listas y valoraciones.') + dialogo.getByText('Entra para acceder a tus compras, favoritos, listas y likes.') ).toBeVisible(); // Sigue siendo /musica: entrar no debería sacarte de donde estabas. await expect(page).toHaveURL(/\/musica$/); diff --git a/scripts/db/comprobar-guardas.mjs b/scripts/db/comprobar-guardas.mjs index 92ce1dc..d11d177 100644 --- a/scripts/db/comprobar-guardas.mjs +++ b/scripts/db/comprobar-guardas.mjs @@ -389,7 +389,7 @@ await permite('y quien administra, sí', `update usuario set rol = 'admin' where apuntar('\nLA VALORACIÓN'); await rechaza( 'no se puede puntuar con un seis', - `insert into valoracion (usuario_id, cancion_id, puntuacion) + `insert into valoracion (usuario_id, cancion_id, "value") values ('u9', 'c1', 6)` ); diff --git a/scripts/db/documentar-modelo.mjs b/scripts/db/documentar-modelo.mjs new file mode 100644 index 0000000..af0ab88 --- /dev/null +++ b/scripts/db/documentar-modelo.mjs @@ -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(); +} diff --git a/src/app.css b/src/app.css index c17c4d5..d6aefe7 100644 --- a/src/app.css +++ b/src/app.css @@ -1,6 +1,7 @@ @import './fuentes.css'; @import './marca.css'; @import './tokens.css'; +@import './controles.css'; /* * --------------------------------------------------------------------------- * DIRECCIÓN VISUAL @@ -59,6 +60,11 @@ /* Los dos extremos del reflejo: la luz que lo cruza y la sombra del canto. */ --c-accent-brillo: var(--c-marca-oro-brillo); --c-accent-sombra: var(--c-marca-oro-sombra); + --c-accent-borde: var(--c-accent-sombra); + /* Los controles finos necesitan contraste incluso en la zona de brillo. */ + --c-control-oro: var(--c-marca-oro); + --c-control-oro-brillo: var(--c-marca-oro-brillo); + --c-control-oro-sombra: var(--c-marca-oro-sombra); /* * El verdemar: el tercer color de la paleta, para datos. @@ -116,23 +122,22 @@ /* * El mismo metal, orientado de izquierda a derecha para controles lineales: - * barras de progreso, volumen y barras de desplazamiento. Usar los tres - * tonos fijos de la marca evita que estos controles parezcan amarillos o - * grises del navegador. + * barras de progreso, volumen y barras de desplazamiento. Esta gama se + * oscurece en claro para que incluso el reflejo se distinga del carril. */ --oro-control: linear-gradient( 90deg, - var(--c-marca-oro-sombra) 0%, - var(--c-marca-oro) 32%, - var(--c-marca-oro-brillo) 52%, - var(--c-marca-oro) 72%, - var(--c-marca-oro-sombra) 100% + var(--c-control-oro-sombra) 0%, + var(--c-control-oro) 32%, + var(--c-control-oro-brillo) 52%, + var(--c-control-oro) 72%, + var(--c-control-oro-sombra) 100% ); --oro-control-pulgar: radial-gradient( circle at 34% 28%, - var(--c-marca-oro-brillo) 0 12%, - var(--c-marca-oro) 46%, - var(--c-marca-oro-sombra) 100% + var(--c-control-oro-brillo) 0 12%, + var(--c-control-oro) 46%, + var(--c-control-oro-sombra) 100% ); /* @@ -208,11 +213,15 @@ --c-text: oklch(0.24 0.055 264); --c-text-muted: oklch(0.48 0.038 264); - --c-accent: oklch(0.52 0.125 74); + --c-accent: oklch(0.42 0.135 70); --c-accent-superficie: oklch(0.76 0.13 82); --c-accent-text: oklch(0.24 0.055 264); --c-accent-brillo: oklch(0.9 0.1 92); - --c-accent-sombra: oklch(0.6 0.12 74); + --c-accent-sombra: oklch(0.63 0.12 74); + --c-accent-borde: var(--c-accent); + --c-control-oro: oklch(0.46 0.1 78); + --c-control-oro-brillo: oklch(0.58 0.11 78); + --c-control-oro-sombra: oklch(0.4 0.09 78); /* En claro se invierten: el dato pintado tiene que ser oscuro sobre papel. */ --c-dato: oklch(0.5 0.085 200); @@ -253,13 +262,12 @@ body { /* * Los desplazamientos forman parte de la interfaz visible. Se pintan con el - * oro fijo del logotipo en lugar del color nativo del sistema, que podía salir - * azul o gris según el navegador. Las reglas WebKit aportan el reflejo metálico; - * `scrollbar-color` mantiene el mismo oro en Firefox. + * oro adaptado al fondo. Las reglas WebKit aportan el reflejo metálico; + * `scrollbar-color` conserva su contraste en Firefox. */ * { scrollbar-width: thin; - scrollbar-color: var(--c-marca-oro) var(--c-bg-sunken); + scrollbar-color: var(--c-control-oro) var(--c-bg-sunken); } *::-webkit-scrollbar { @@ -276,12 +284,12 @@ body { min-width: 2.5rem; border: 2px solid var(--c-bg-sunken); border-radius: var(--radius-round); - background-color: var(--c-marca-oro); + background-color: var(--c-control-oro); background-image: var(--oro-control); } *::-webkit-scrollbar-thumb:hover { - background-color: var(--c-marca-oro-brillo); + background-color: var(--c-control-oro-brillo); } /* @@ -294,13 +302,13 @@ body { -webkit-appearance: none; height: 1rem; background: transparent; - accent-color: var(--c-marca-oro); + accent-color: var(--c-control-oro); cursor: pointer; } .rango-dorado::-webkit-slider-runnable-track { height: 5px; - border: 1px solid color-mix(in oklab, var(--c-marca-oro-sombra) 55%, var(--c-border)); + border: 1px solid var(--c-control-oro-sombra); border-radius: var(--radius-round); background-color: var(--c-bg-sunken); background-image: var(--oro-control); @@ -310,7 +318,7 @@ body { .rango-dorado::-moz-range-track { height: 5px; - border: 1px solid color-mix(in oklab, var(--c-marca-oro-sombra) 55%, var(--c-border)); + border: 1px solid var(--c-control-oro-sombra); border-radius: var(--radius-round); background: var(--c-bg-sunken); } @@ -318,7 +326,7 @@ body { .rango-dorado::-moz-range-progress { height: 5px; border-radius: var(--radius-round); - background-color: var(--c-marca-oro); + background-color: var(--c-control-oro); background-image: var(--oro-control); } @@ -328,9 +336,9 @@ body { width: 14px; height: 14px; margin-top: calc((var(--sp-1) + var(--gap-hairline)) * -1); - border: 1px solid var(--c-marca-oro-sombra); + border: 1px solid var(--c-control-oro-sombra); border-radius: var(--radius-round); - background-color: var(--c-marca-oro); + background-color: var(--c-control-oro); background-image: var(--oro-control-pulgar); box-shadow: 0 0 0 2px var(--c-bg-raised); } @@ -338,9 +346,9 @@ body { .rango-dorado::-moz-range-thumb { width: 14px; height: 14px; - border: 1px solid var(--c-marca-oro-sombra); + border: 1px solid var(--c-control-oro-sombra); border-radius: var(--radius-round); - background-color: var(--c-marca-oro); + background-color: var(--c-control-oro); background-image: var(--oro-control-pulgar); box-shadow: 0 0 0 2px var(--c-bg-raised); } @@ -351,7 +359,7 @@ body { .rango-dorado:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 2px var(--c-bg-raised), - 0 0 0 4px color-mix(in oklab, var(--c-marca-oro) 25%, transparent); + 0 0 0 4px color-mix(in oklab, var(--c-control-oro) 25%, transparent); } h1, @@ -450,8 +458,6 @@ a:hover { } /* Los formularios usan azul; las acciones de reproducción conservan el oro. */ -form .btn--primary, -.programador .btn--primary, .btn.btn--form-primary { background: var(--c-form-action); border-color: var(--c-form-action); @@ -461,8 +467,6 @@ form .btn--primary, background-color var(--duration-normal) var(--ease-standard), border-color var(--duration-normal) var(--ease-standard); } -form .btn--primary:hover, -.programador .btn--primary:hover, .btn.btn--form-primary:hover { background: var(--c-form-action-hover); border-color: var(--c-form-action-hover); @@ -543,13 +547,6 @@ button { } .btn { - display: inline-flex; - align-items: center; - gap: var(--sp-2); - min-height: var(--control-height); - padding: var(--boton-relleno); - border: 1px solid var(--c-border); - border-radius: var(--radius-boton); /* * `background-color` y no el atajo `background`: el atajo reinicia también * `background-image`, así que una variante con degradado —el botón dorado— @@ -557,11 +554,6 @@ button { * en un rectángulo oscuro con el borde de oro. */ background-color: transparent; - font-size: var(--boton-texto); - letter-spacing: 0.01em; - cursor: pointer; - text-decoration: none; - line-height: var(--leading-ui); transition: background-color 0.2s var(--ease-standard), border-color 0.2s var(--ease-standard); @@ -680,7 +672,7 @@ button { /* El canto sí es del botón: dentro de una barra no hay borde que teñir. */ .btn--primary { - border-color: var(--c-accent-sombra); + border-color: var(--c-accent-borde); } /* @@ -782,6 +774,25 @@ a.chip:focus-visible { box-shadow 0.42s var(--ease-emphasized); } +/* + * El selector de listas es un panel superpuesto que nace dentro de la tarjeta. + * Mientras está abierto debe poder rebasar sus bordes y quedar sobre las + * tarjetas vecinas; al cerrarlo se recupera el recorte de portada y efectos. + */ +.tarjeta:has(details[open]) { + z-index: 10; + overflow: visible; +} + +.tarjeta:has(details[open])::before, +.tarjeta:has(details[open])::after { + border-radius: inherit; +} + +.tarjeta:has(details[open]) .tarjeta__medio { + border-radius: calc(var(--radius-tarjeta) - 1px) calc(var(--radius-tarjeta) - 1px) 0 0; +} + /* * El reflejo. Nace donde está el puntero —lo escribe la acción `brillo` en * `--mx` y `--my`— y por eso no se lee como un efecto enlatado. Sin JavaScript @@ -1014,12 +1025,18 @@ a.chip:focus-visible { * controles, proporcionada al título y al resto de sus acciones. */ .tarjeta .tarjeta__reproduccion button { - min-width: var(--control-height-compact); - min-height: var(--control-height-compact); - width: var(--control-height-compact); - height: var(--control-height-compact); + --lado-play: var(--control-height-compact); + min-width: var(--lado-play); + min-height: var(--lado-play); + width: var(--lado-play); + height: var(--lado-play); padding: 0; } +@media (pointer: coarse) { + .tarjeta .tarjeta__reproduccion button { + --lado-play: var(--control-height); + } +} /* Lo que se apoya sobre la tarjeta y tiene que quedar por encima del enlace. */ .tarjeta__encima { diff --git a/src/controles.css b/src/controles.css new file mode 100644 index 0000000..727ecab --- /dev/null +++ b/src/controles.css @@ -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); + } +} diff --git a/src/estudio/components/CampoEditor.svelte b/src/estudio/components/CampoEditor.svelte index bdf3b41..9324046 100644 --- a/src/estudio/components/CampoEditor.svelte +++ b/src/estudio/components/CampoEditor.svelte @@ -1,4 +1,5 @@ + +{#if fallida} + Archivo no disponible +{:else if clase === 'imagen'} + {nombre} (fallida = true)} /> +{:else} + +{/if} + + diff --git a/src/estudio/estudio.css b/src/estudio/estudio.css index 38a8d62..25a8641 100644 --- a/src/estudio/estudio.css +++ b/src/estudio/estudio.css @@ -1,6 +1,7 @@ @import '../fuentes.css'; @import '../marca.css'; @import '../tokens.css'; +@import '../controles.css'; :root { font-size: var(--fs-base); line-height: 1.5; @@ -14,7 +15,7 @@ --borde: var(--c-border); --texto: var(--c-text); --muted: var(--c-text-muted); - --acento: var(--c-marca-oro); + --acento: var(--ui-action); --suave: var(--c-bg-sunken); } * { @@ -61,18 +62,18 @@ p { margin-top: 0; } h1 { - font-family: var(--font-display); - font-size: var(--fs-2xl); - letter-spacing: -0.045em; - font-weight: var(--font-weight-semibold); - margin-bottom: var(--sp-3); + font: var(--ui-page-font); + letter-spacing: -0.025em; + margin-bottom: var(--sp-2); } h2 { - font-size: var(--fs-xl); - letter-spacing: -0.02em; + font: var(--ui-section-font); + letter-spacing: -0.015em; + margin-bottom: var(--sp-3); } h3 { - font-size: var(--fs-lg); + font: var(--ui-panel-font); + margin-bottom: var(--sp-3); } p { line-height: 1.65; @@ -82,30 +83,18 @@ small, color: var(--muted); } small { - font-size: var(--fs-base); + font: var(--ui-meta-font); } .eyebrow { - font-size: var(--fs-base); + font-size: var(--fs-xs); letter-spacing: 0.06em; text-transform: uppercase; - font-weight: var(--font-weight-semibold); + font-weight: var(--font-weight-medium); color: var(--muted); } .boton { - display: inline-flex; - align-items: center; - justify-content: center; - gap: var(--sp-3); - min-height: var(--control-height); - border: 1px solid var(--borde); - border-radius: var(--radius-boton); - padding: var(--sp-3) var(--sp-4); background: var(--papel); color: var(--texto); - font-weight: 600; - font-size: var(--fs-base); - text-decoration: none; - white-space: nowrap; } .boton:hover { background: var(--suave); @@ -113,31 +102,26 @@ small { } .boton.primario { background: var(--acento); - color: var(--c-bg); + color: var(--ui-action-text); border-color: var(--acento); } .boton.primario:hover { - background: var(--c-marca-oro-brillo); + background: var(--ui-action-hover); } .boton.peligro { color: var(--c-danger); } -.boton.pequeno { - font-size: var(--fs-base); - min-height: var(--control-height-compact); - padding: var(--sp-2) var(--sp-3); -} .cabecera-pagina { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-5); - margin-bottom: var(--sp-6); + margin-bottom: var(--sp-5); } .cabecera-pagina p { margin: 0; color: var(--muted); - font-size: var(--fs-base); + font: var(--ui-label-font); } .acciones { display: flex; @@ -163,7 +147,7 @@ small { gap: var(--sp-2); border-radius: var(--radius-detail); padding: var(--sp-1) var(--sp-2); - font-size: var(--fs-base); + font: var(--ui-meta-font); background: var(--papel); color: var(--texto); white-space: nowrap; @@ -177,15 +161,15 @@ small { } .estado.publicado { background: var(--papel); - color: var(--texto); + color: var(--ui-status-success); } .estado.borrador { background: var(--suave); - color: var(--acento); + color: var(--ui-status-draft); } .estado.retirado { background: var(--papel); - color: var(--texto); + color: var(--muted); } .aviso { border: 1px solid var(--borde); @@ -209,8 +193,7 @@ small { label { display: grid; gap: var(--sp-2); - font-size: var(--fs-base); - font-weight: 600; + font: var(--ui-label-font); } input:not([type='checkbox']):not([type='file']), select, @@ -218,7 +201,8 @@ textarea { width: 100%; border: 1px solid var(--borde); border-radius: var(--radius-boton); - padding: var(--sp-3) var(--sp-4); + padding: var(--ui-input-padding); + font: var(--ui-input-font); background: var(--papel); color: var(--texto); min-height: var(--control-height); @@ -267,7 +251,8 @@ input[type='checkbox'] { padding: var(--sp-4) var(--sp-5); display: flex; gap: var(--sp-3); - align-items: center; + align-items: end; + flex-wrap: wrap; border-bottom: 1px solid var(--borde); } .filtros input { @@ -289,10 +274,10 @@ table { th { text-align: left; text-transform: uppercase; - font-size: var(--fs-base); + font-size: var(--fs-xs); letter-spacing: 0.03em; - color: var(--texto); - font-weight: var(--font-weight-semibold); + color: var(--muted); + font-weight: var(--font-weight-medium); background: var(--papel); } th, @@ -336,7 +321,7 @@ td .titulo { align-items: center; padding: var(--sp-4) var(--sp-5); border-top: 1px solid var(--borde); - font-size: var(--fs-base); + font: var(--ui-meta-font); } .vacio { padding: calc(var(--sp-6) * 2) var(--sp-6); @@ -355,9 +340,9 @@ td .titulo { display: flex; gap: var(--sp-2); align-items: center; - font-size: var(--fs-base); + font: var(--ui-meta-font); color: var(--muted); - margin-bottom: var(--sp-5); + margin-bottom: var(--sp-3); flex-wrap: wrap; } .tabs { @@ -365,12 +350,12 @@ td .titulo { gap: var(--sp-5); overflow: auto; border-bottom: 1px solid var(--borde); - margin-bottom: var(--sp-6); + margin-bottom: var(--sp-5); } .tabs a, .tabs button { padding: var(--sp-4) var(--sp-1); - font-size: var(--fs-base); + font: var(--ui-label-font); border: 0; border-bottom: 2px solid transparent; background: transparent; @@ -393,12 +378,15 @@ td .titulo { display: grid; gap: var(--sp-4); } +.editor-lateral h2 { + font: var(--ui-panel-font); +} .editor-lateral p { - font-size: var(--fs-base); + font-size: var(--fs-sm); } .editor-lateral ul { padding-left: var(--sp-4); - font-size: var(--fs-base); + font-size: var(--fs-sm); line-height: 1.8; } .editor-barra { @@ -487,7 +475,7 @@ dialog form { scroll-margin-top: calc(var(--sp-7) * 2); } h1 { - font-size: var(--fs-2xl); + font-size: var(--fs-xl); } .cabecera-pagina { align-items: flex-start; @@ -531,6 +519,15 @@ dialog form { border-radius: var(--radius-boton); background: var(--papel); color: var(--texto); - font-size: var(--fs-base); + font: var(--ui-meta-font); white-space: nowrap; } + +.filtros .filtro { + flex: 1 1 12rem; + min-width: 0; +} +.filtros .filtro select { + width: 100%; + max-width: none; +} diff --git a/src/estudio/rutas/(dentro)/+layout.svelte b/src/estudio/rutas/(dentro)/+layout.svelte index dd14b16..d96d6a9 100644 --- a/src/estudio/rutas/(dentro)/+layout.svelte +++ b/src/estudio/rutas/(dentro)/+layout.svelte @@ -1,5 +1,6 @@ + + diff --git a/src/estudio/rutas/(dentro)/buscar/+page.svelte b/src/estudio/rutas/(dentro)/buscar/+page.svelte index 5f94c67..d7576d5 100644 --- a/src/estudio/rutas/(dentro)/buscar/+page.svelte +++ b/src/estudio/rutas/(dentro)/buscar/+page.svelte @@ -14,13 +14,15 @@
- +
diff --git a/src/estudio/rutas/(dentro)/medios/+page.svelte b/src/estudio/rutas/(dentro)/medios/+page.svelte index 533c382..7170ec5 100644 --- a/src/estudio/rutas/(dentro)/medios/+page.svelte +++ b/src/estudio/rutas/(dentro)/medios/+page.svelte @@ -1,5 +1,6 @@ {#if cancion.alaVenta} @@ -39,14 +60,25 @@ {compacto ? 'Tuya' : 'Ya es tuya'} {:else} - + - @@ -55,17 +87,14 @@ diff --git a/src/lib/components/BotonFavorito.svelte b/src/lib/components/BotonFavorito.svelte index a5806c5..d6fba04 100644 --- a/src/lib/components/BotonFavorito.svelte +++ b/src/lib/components/BotonFavorito.svelte @@ -11,9 +11,11 @@ * de guardar. Esconderlo dejaría la interfaz cambiando de forma según quién * mira, y no explicaría por qué falta. */ - import { enhance } from '$app/forms'; + import { applyAction, enhance } from '$app/forms'; + import { invalidateAll } from '$app/navigation'; import { resolve } from '$app/paths'; import { page } from '$app/state'; + import type { SubmitFunction } from '@sveltejs/kit'; import { pedirAcceso } from '$lib/acceso.svelte'; import Icono from './Icono.svelte'; @@ -24,7 +26,7 @@ tamano?: number; } - let { cancionSlug, compacto = false, tamano = 18 }: Props = $props(); + let { cancionSlug, compacto = false, tamano = 20 }: Props = $props(); // Con `?.`: en una página de error el `load` del layout puede no haberse // ejecutado, y el botón tiene que pintarse igual. @@ -35,10 +37,33 @@ const volverA = $derived(page.url.pathname + page.url.search); const texto = $derived(guardado ? 'Quitar de favoritos' : 'Añadir a favoritos'); + let enviando = $state(false); + + /** + * La acción conserva la redirección para funcionar sin JavaScript. Aquí la + * interceptamos y refrescamos solo los datos del layout: navegar de nuevo a + * la URL actual mandaría el scroll al principio de la página. + */ + const enviar: SubmitFunction = ({ cancel }) => { + if (enviando) { + cancel(); + return; + } + enviando = true; + return async ({ result, update }) => { + try { + if (result.type === 'redirect') await invalidateAll(); + else if (result.type === 'failure' || result.type === 'error') await applyAction(result); + else await update({ reset: false, invalidateAll: true }); + } finally { + enviando = false; + } + }; + }; {#if usuario} - + @@ -230,7 +230,7 @@ /> - @@ -289,15 +289,14 @@ } label { - font-size: var(--fs-sm); - font-weight: 600; + font: var(--ui-label-font); } input { - font: inherit; + font: var(--ui-input-font); color: inherit; width: 100%; - padding: var(--sp-3); + padding: var(--ui-input-padding); border: 1px solid var(--c-border); border-radius: var(--radius-boton); background: var(--c-bg); diff --git a/src/lib/components/FormularioBusqueda.svelte b/src/lib/components/FormularioBusqueda.svelte index cc3e15f..8c3f726 100644 --- a/src/lib/components/FormularioBusqueda.svelte +++ b/src/lib/components/FormularioBusqueda.svelte @@ -19,7 +19,7 @@ minlength="2" maxlength={LONGITUD_BUSQUEDA} /> - @@ -69,12 +69,12 @@ width: 100%; min-width: 0; min-height: var(--control-height-touch); - padding: var(--sp-3); + padding: var(--ui-input-padding); border: 0; border-radius: var(--radius-boton) 0 0 var(--radius-boton); background: transparent; color: var(--c-text); - font: inherit; + font: var(--ui-input-font); } input:focus-visible { outline: none; @@ -98,12 +98,12 @@ width: 100%; min-width: 0; min-height: var(--control-height); - padding: var(--sp-2); + padding: var(--ui-input-padding); border: 1px solid var(--c-border); border-radius: var(--radius-boton); background: var(--c-bg-sunken); color: var(--c-text); - font: inherit; + font: var(--ui-input-font); font-weight: 400; } @media (max-width: 34rem) { diff --git a/src/lib/components/FormularioContacto.svelte b/src/lib/components/FormularioContacto.svelte index ef7800e..9dfa5a7 100644 --- a/src/lib/components/FormularioContacto.svelte +++ b/src/lib/components/FormularioContacto.svelte @@ -159,7 +159,7 @@ {/if} - @@ -186,8 +186,7 @@ } label { - font-size: var(--fs-sm); - font-weight: 600; + font: var(--ui-label-font); } input, @@ -195,12 +194,12 @@ textarea { width: 100%; min-height: var(--control-height); - padding: var(--sp-2) var(--sp-3); + padding: var(--ui-input-padding); border: 1px solid var(--c-border); border-radius: var(--radius-boton); background: var(--c-bg); color: inherit; - font: inherit; + font: var(--ui-input-font); } textarea { diff --git a/src/lib/components/GaleriaControles.svelte b/src/lib/components/GaleriaControles.svelte new file mode 100644 index 0000000..3395fa4 --- /dev/null +++ b/src/lib/components/GaleriaControles.svelte @@ -0,0 +1,136 @@ + + +Galería de controles · Senza Paura +
+
+

Referencia visual · Desarrollo

+

Controles y jerarquía

+

+ Ejemplos sin acciones de guardado. Comprueba los estados con ratón y teclado y reduce el ancho + de la ventana. +

+
+
+

Acciones

+
+ + + + + +
+
+ {#if administracion} +
+

Campos y mensajes

+
+ + +
+

+ PublicadoBorradorRetirado +

+ +
+ {/if} +
+

Sección

+

Panel secundario

+

Texto de lectura y contenido principal.

+ Fecha, duración o información auxiliar. +
+
+

Comprobación en el navegador

+ + {#if resultado}

+ {resultado.length + ? resultado.join(' ') + : 'Sin desviaciones de tipografía, altura ni desbordamiento en las muestras.'} +

{/if} +
+
+ + diff --git a/src/lib/components/Heroe.svelte b/src/lib/components/Heroe.svelte index f112e95..5f1f87a 100644 --- a/src/lib/components/Heroe.svelte +++ b/src/lib/components/Heroe.svelte @@ -140,14 +140,18 @@ width: 100%; } + .texto { + container-type: inline-size; + } + h1 { - font-size: var(--fs-4xl); + font-size: var(--fs-hero-title); margin-bottom: var(--sp-4); } .entradilla { max-width: 40ch; - font-size: var(--fs-lg); + font-size: var(--fs-hero-lead); color: var(--c-text-muted); } @@ -185,18 +189,30 @@ h1 { margin-bottom: var(--sp-2); } - .entradilla { - font-size: var(--fs-base); - } .disposicion { - grid-template-columns: minmax(0, 1fr); + grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: var(--sp-6); } - /* En móvil la escucha queda junto al título, antes del tocadiscos. */ .plato { - max-width: min(75vw, 22rem); + /* Un ancho definido evita que el hijo porcentual colapse con los márgenes auto. */ + width: 100%; + max-width: 22rem; margin-inline: auto; } } + + @media (max-width: 48rem) { + .heroe { + /* Reserva los mandos del carrusel bajo el disco en la disposición vertical. */ + padding-bottom: calc(var(--sp-5) * 2 + var(--control-height) + var(--sp-2) + 2px); + } + .disposicion { + grid-template-columns: minmax(0, 1fr); + } + /* En móvil la escucha queda junto al título, antes del tocadiscos. */ + .plato { + width: min(75vw, 22rem); + } + } diff --git a/src/lib/components/Icono.svelte b/src/lib/components/Icono.svelte index 552e487..68b01e1 100644 --- a/src/lib/components/Icono.svelte +++ b/src/lib/components/Icono.svelte @@ -8,6 +8,29 @@ type Trazo = { tipo: 'relleno' | 'linea'; d: string[] }; const ICONOS = { + subir: { tipo: 'linea', d: ['M12 20V4', 'M5 11l7-7 7 7'] }, + bajar: { tipo: 'linea', d: ['M12 4v16', 'M5 13l7 7 7-7'] }, + inicio: { tipo: 'linea', d: ['M3 10 12 3l9 7', 'M5 9v12h5v-7h4v7h5V9'] }, + mas: { tipo: 'linea', d: ['M12 5v14', 'M5 12h14'] }, + documento: { tipo: 'linea', d: ['M6 3h8l4 4v14H6Z', 'M14 3v5h4', 'M9 12h6', 'M9 16h6'] }, + musica: { + tipo: 'linea', + d: [ + 'M9 18V5l11-2v13', + 'M9 5v4l11-2', + 'M9 18a3 2 0 1 1-6 0 3 2 0 1 1 6 0', + 'M20 16a3 2 0 1 1-6 0 3 2 0 1 1 6 0' + ] + }, + 'microfono-escenario': { + tipo: 'linea', + d: [ + 'M12 2a4 4 0 0 0-4 4v6a4 4 0 0 0 8 0V6a4 4 0 0 0-4-4Z', + 'M5 10v2a7 7 0 0 0 14 0v-2', + 'M12 19v3', + 'M8 22h8' + ] + }, play: { tipo: 'relleno', d: ['M8 5v14l11-7z'] }, pausa: { tipo: 'relleno', d: ['M7 4h3.5v16H7z', 'M13.5 4H17v16h-3.5z'] }, detener: { tipo: 'relleno', d: ['M6 6h12v12H6z'] }, @@ -103,6 +126,18 @@ luna: { tipo: 'linea', d: ['M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z'] }, // Dos versiones del mismo trazo: el hueco para lo no marcado y el relleno // para lo marcado. La diferencia se ve de un vistazo sin leer nada. + 'me-gusta': { + tipo: 'linea', + d: ['M7 10v11H3V10Z', 'M7 10l5-7h1a2 2 0 0 1 2 2v4h4a2 2 0 0 1 2 2l-2 8a2 2 0 0 1-2 2H7'] + }, + 'me-gusta-lleno': { + tipo: 'relleno', + d: ['M7 10v11H3V10Z', 'M7 10l5-7h1a2 2 0 0 1 2 2v4h4a2 2 0 0 1 2 2l-2 8a2 2 0 0 1-2 2H7Z'] + }, + compartir: { + tipo: 'linea', + d: ['M4 12v8h16v-8', 'M12 16V3', 'M7 8l5-5 5 5'] + }, corazon: { tipo: 'linea', d: [ diff --git a/src/lib/components/PortadaReproducible.svelte b/src/lib/components/PortadaReproducible.svelte new file mode 100644 index 0000000..daeb2be --- /dev/null +++ b/src/lib/components/PortadaReproducible.svelte @@ -0,0 +1,79 @@ + + +
+ {#if portada && !fallida} (fallida = true)} + /> + {:else}{/if} +
+
+ + diff --git a/src/lib/components/TablaAlbumes.svelte b/src/lib/components/TablaAlbumes.svelte index c8edbc8..57e655b 100644 --- a/src/lib/components/TablaAlbumes.svelte +++ b/src/lib/components/TablaAlbumes.svelte @@ -2,13 +2,14 @@ import { resolve } from '$app/paths'; import type { AlbumConCanciones } from '$lib/types'; import { nombresTipoAlbum } from '$lib/albumes'; + import { interpretesUnicos } from '$lib/artistas'; import { obtenerEstilo } from '$lib/catalogo/cliente.svelte'; import { aPistas } from '$lib/player/pista'; import { reproductor } from '$lib/player/player.svelte'; import { ano } from '$lib/utils/date'; import { segundosADuracion } from '$lib/utils/format'; - import BotonReproducir from './BotonReproducir.svelte'; - import SinPortada from './SinPortada.svelte'; + import PortadaReproducible from './PortadaReproducible.svelte'; + import Icono from './Icono.svelte'; import TablaCatalogo from './TablaCatalogo.svelte'; let { albumes }: { albumes: AlbumConCanciones[] } = $props(); @@ -16,7 +17,8 @@ albumes.map((album) => ({ album, estilo: obtenerEstilo(album.estilo), - pistas: aPistas(album.canciones) + pistas: aPistas(album.canciones), + interpretes: interpretesUnicos(album.canciones) })) ); @@ -26,31 +28,24 @@ - + - {#each filas as { album, estilo, pistas } (album.slug)} + {#each filas as { album, estilo, pistas, interpretes } (album.slug)} {@const duracion = album.duracionTotal > 0 ? segundosADuracion(album.duracionTotal) : '—'} reproductor.esActual(pista.id))}> - + @@ -83,6 +114,7 @@ diff --git a/src/lib/components/TablaCatalogo.svelte b/src/lib/components/TablaCatalogo.svelte index da10b33..8a2d28e 100644 --- a/src/lib/components/TablaCatalogo.svelte +++ b/src/lib/components/TablaCatalogo.svelte @@ -21,6 +21,12 @@ width: 100%; overflow-x: auto; } + .marco:has(:global(details[open])) { + position: relative; + z-index: 20; + contain: none; + overflow: visible; + } table { width: 100%; border-collapse: separate; @@ -118,6 +124,10 @@ table :global(a:hover) { text-decoration: underline; } + table :global(.selector .panel) { + right: 0; + left: auto; + } table :global(.titulo) { font-size: var(--fs-base); font-weight: var(--font-weight-regular); diff --git a/src/lib/components/TarjetaAlbum.svelte b/src/lib/components/TarjetaAlbum.svelte index acfeadb..8bb5b7f 100644 --- a/src/lib/components/TarjetaAlbum.svelte +++ b/src/lib/components/TarjetaAlbum.svelte @@ -15,6 +15,8 @@ import { ano } from '$lib/utils/date'; import { duracionLarga } from '$lib/utils/format'; import { nombresTipoAlbum } from '$lib/albumes'; + import { interpretesUnicos } from '$lib/artistas'; + import Icono from './Icono.svelte'; interface Props { album: AlbumConCanciones; @@ -29,6 +31,7 @@ let { album, retraso = 0, compacta = false, nivelTitulo = 3 }: Props = $props(); const pistas = $derived(aPistas(album.canciones)); + const interpretes = $derived(interpretesUnicos(album.canciones));
@@ -59,6 +62,28 @@ + {#if interpretes.length > 0} +

+ + + {#each interpretes as interprete, i (interprete.id)} + {#if i > 0}{/if} + {#if interprete.slug} + {interprete.nombre} + {:else} + {interprete.nombre} + {/if} + {/each} + +

+ {:else} +

Sin acreditar

+ {/if} +

{ano(album.fecha)} @@ -98,7 +123,7 @@ .titulo { margin: 0; - font-size: var(--fs-xl); + font-size: var(--fs-lg); } .titulo a { @@ -106,8 +131,40 @@ } .meta { + margin: 0; + font-size: var(--fs-ui-meta); + } + + .interpretes { + display: flex; + align-items: flex-start; + gap: var(--sp-2); margin: 0; font-size: var(--fs-sm); + line-height: 1.35; + } + + .icono-interprete { + flex: none; + color: var(--c-accent); + } + + .nombres-interpretes { + display: -webkit-box; + overflow: hidden; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + line-clamp: 2; + } + + .interpretes a { + color: var(--c-text); + text-decoration: none; + } + + .interpretes a:hover, + .interpretes a:focus-visible { + text-decoration: underline; } .resumen { diff --git a/src/lib/components/TarjetaCancion.svelte b/src/lib/components/TarjetaCancion.svelte index 0b08031..a687185 100644 --- a/src/lib/components/TarjetaCancion.svelte +++ b/src/lib/components/TarjetaCancion.svelte @@ -13,9 +13,12 @@ import { resolve } from '$app/paths'; import { page } from '$app/state'; import BotonCarrito from './BotonCarrito.svelte'; + import BotonCompartirCancion from './BotonCompartirCancion.svelte'; import BotonFavorito from './BotonFavorito.svelte'; + import BotonMeGusta from './BotonMeGusta.svelte'; import BotonListaReproduccion from './BotonListaReproduccion.svelte'; import BotonReproducir from './BotonReproducir.svelte'; + import Icono from './Icono.svelte'; import { brillo } from '$lib/acciones/brillo'; import { revelar } from '$lib/acciones/revelar'; import { @@ -42,7 +45,6 @@ const album = $derived(obtenerAlbum(albumDeCancion(cancion))); const estilo = $derived(obtenerEstilo(cancion.estilo)); - const valoracion = $derived(page.data.valoraciones?.[cancion.slug]); /** Un corte de un álbum hereda visualmente el año de publicación del disco. */ const ano = $derived(cancion.ano ?? (album?.fecha ? Number(album.fecha.slice(0, 4)) : undefined)); const enlace = $derived(resolve('/(sitio)/canciones/[slug]', { slug: cancion.slug })); @@ -77,6 +79,27 @@ {cancion.versiones.find((v) => v.id === cancion.versionEnLanzamiento)?.nombre}

{/if} + {#if cancion.interpretes.length > 0} +

+ + + {#each cancion.interpretes as interprete, i (interprete.id + interprete.rol)} + {#if i > 0}{/if} + {#if interprete.slug} + {interprete.nombre} + {:else} + {interprete.nombre} + {/if} + {/each} + +

+ {:else} +

Sin acreditar

+ {/if}

{#if album}{album.titulo}{/if} @@ -90,19 +113,6 @@ {/if}

-

- - {valoracion ? valoracion.media.toFixed(1) : 'Sin valorar'} - {#if valoracion} - ({valoracion.cuantas}) - {/if} -

-
@@ -111,6 +121,8 @@ listas={page.data.listas ?? []} tamano={20} /> + +
@@ -129,43 +141,46 @@ .meta { margin: 0; - font-size: var(--fs-xs); + font-size: var(--fs-ui-meta); } - .nota { + .interpretes { display: flex; align-items: center; - gap: var(--sp-1); - margin-top: auto; - margin-inline: 0; - padding-top: var(--sp-1); - font-family: var(--font-body); + gap: var(--sp-2); + margin: 0; font-size: var(--fs-sm); - font-variant-numeric: tabular-nums; + line-height: 1.3; } - .estrella { + .icono-interprete { + flex: none; color: var(--c-accent); - font-size: var(--fs-lg); - line-height: 1; } - .votos { - color: var(--c-text-muted); + .interpretes a { + color: var(--c-text); + text-decoration: none; + } + + .interpretes a:hover, + .interpretes a:focus-visible { + text-decoration: underline; } .pie { display: flex; align-items: center; - gap: var(--sp-2); + gap: 0; justify-content: flex-start; + margin-top: auto; padding-top: var(--sp-3); } .acciones-personales { display: flex; align-items: center; - gap: var(--sp-1); + gap: 0; } /* diff --git a/src/lib/components/TarjetaCancion.svelte.spec.ts b/src/lib/components/TarjetaCancion.svelte.spec.ts index 8f027ec..f2dc9cb 100644 --- a/src/lib/components/TarjetaCancion.svelte.spec.ts +++ b/src/lib/components/TarjetaCancion.svelte.spec.ts @@ -47,7 +47,8 @@ vi.mock('$app/state', () => ({ carrito: [], compras: [], favoritos: [], - valoraciones: { 'como-si-nada': { media: 4.3, cuantas: 12 } }, + likes: { 'como-si-nada': 12 }, + misLikes: [], suscripcionActiva: false, accesosPrivados: [] }; @@ -160,12 +161,13 @@ describe('TarjetaCancion', () => { expect(enlace.element().querySelector('svg')?.getAttribute('width')).toBe('20'); }); - it('muestra la nota media y el número de votos', async () => { + it('muestra el número de likes en el tooltip, sin estrellas ni nota media', async () => { render(TarjetaCancion, { cancion: cancion(), pistas: [pista()], indice: 0 }); - await expect - .element(page.getByLabelText('Valoración: 4.3 de 5, 12 votos')) - .toHaveTextContent(/★\s+4\.3\s+\(12\)/); + const meGusta = page.getByRole('link', { name: 'Me gusta: 12 likes. Inicia sesión' }); + await expect.element(meGusta).toHaveAttribute('title', 'Me gusta: 12 likes'); + await expect.element(meGusta).not.toHaveTextContent('12'); + await expect.element(page.getByText('★')).not.toBeInTheDocument(); }); it('no ofrece comprar lo que no está a la venta', async () => { diff --git a/src/lib/components/jukebox/ProgramadorJukebox.svelte b/src/lib/components/jukebox/ProgramadorJukebox.svelte index 34c988e..a0e1d93 100644 --- a/src/lib/components/jukebox/ProgramadorJukebox.svelte +++ b/src/lib/components/jukebox/ProgramadorJukebox.svelte @@ -20,6 +20,7 @@ import { cancionesDeFranja, validarProgramacion, + usaEtiquetas, type FranjaJukebox, type FuenteJukebox } from '$lib/programacion-jukebox'; @@ -30,6 +31,7 @@ import GrupoToggle from '../GrupoToggle.svelte'; import Icono from '../Icono.svelte'; import SelectorContenido from './SelectorContenido.svelte'; + import SelectorEtiquetas from './SelectorEtiquetas.svelte'; let { estado = $bindable(), listas, @@ -55,11 +57,9 @@ [...estado.programacion.franjas].sort((a, b) => a.inicio.localeCompare(b.inicio)) ); function temas(f: FranjaJukebox) { - return cancionesDeFranja( - f, - listas, - canciones.map((c) => c.slug) - ).flatMap((slug) => canciones.find((c) => c.slug === slug) ?? []); + return cancionesDeFranja(f, listas, canciones).flatMap( + (slug) => canciones.find((c) => c.slug === slug) ?? [] + ); } const vacias = $derived(estado.programacion.franjas.filter((f) => temas(f).length === 0)); function nuevaFranja() { @@ -368,19 +368,34 @@ Contenido { - franja.catalogo = valor === 'catalogo'; - if (franja.catalogo) anadiendo = false; + franja.catalogo = valor !== 'seleccion'; + if (valor === 'etiquetas') franja.etiquetas ??= []; + if (valor === 'catalogo') delete franja.etiquetas; + anadiendo = false; }} /> - {#if franja.catalogo}
+ + {/each} + + + {#if artistas.length === 0} +

+ No hay intérpretes que coincidan con los criterios seleccionados. +

+ {/if} + + {:else} +

Aún no hay intérpretes publicados.

+ {/if} + + { + if (evento.target === dialogoFiltros) cerrarFiltros(); + }} +> +
+
+

Filtrar intérpretes

+ +
+ +
+
+ diff --git a/src/routes/(sitio)/interpretes/[slug]/+page.svelte b/src/routes/(sitio)/interpretes/[slug]/+page.svelte index f3d4353..9b9ff3a 100644 --- a/src/routes/(sitio)/interpretes/[slug]/+page.svelte +++ b/src/routes/(sitio)/interpretes/[slug]/+page.svelte @@ -201,7 +201,8 @@ width: 100%; height: 100%; object-fit: cover; - object-position: center; + /* Conserva la zona segura generada sobre la cabeza bajo la cabecera superpuesta. */ + object-position: center top; } .heroe.con-fondo { @@ -211,8 +212,8 @@ .con-fondo .heroe__interior { grid-template-columns: minmax(0, 54rem); align-items: end; - min-height: 70vh; - min-height: 70dvh; + min-height: 90vh; + min-height: 90dvh; } .con-fondo .presentacion { @@ -384,8 +385,9 @@ .albumes { display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 20rem)); gap: var(--sp-5); + align-items: start; } .ficha-tecnica { @@ -427,10 +429,6 @@ grid-template-columns: minmax(10rem, 18rem) minmax(0, 1fr); min-height: 0; } - - .albumes { - grid-template-columns: minmax(0, 1fr); - } } @media (max-width: 42rem) { diff --git a/src/routes/(sitio)/juke-box/+page.svelte b/src/routes/(sitio)/juke-box/+page.svelte index 1b5c7a1..d66aeb1 100644 --- a/src/routes/(sitio)/juke-box/+page.svelte +++ b/src/routes/(sitio)/juke-box/+page.svelte @@ -1,247 +1,6 @@ - { - if (desplegado && e.target instanceof Node && !selector?.contains(e.target)) desplegado = false; - }} - onkeydown={(e) => { - if (e.key === 'Escape' && desplegado) { - desplegado = false; - botonPlay?.focus(); - } - }} -/> - - - {#snippet play()} -
- - {#if desplegado} -
- {#if data.jukeboxes.length} -
    - {#each data.jukeboxes as j (j.id)} -
  • - -
  • - {/each} -
- {:else} -

Crea tu primer jukebox para elegir qué quieres escuchar.

- Configura tu Jukebox - {/if} -
- {/if} -
- {/snippet} - {#snippet estado()} - {#if reproductor.programacion} -
-

- {reproductor.programacion.nombre} - {reproductor.estadoJukebox} - -

- {#if !reproductor.programacionDetenida} - - {/if} -
- {/if} - {/snippet} -
- - + diff --git a/src/routes/(sitio)/juke-box/[id]/programacion/+server.ts b/src/routes/(sitio)/juke-box/[id]/programacion/+server.ts index a39e68a..e668542 100644 --- a/src/routes/(sitio)/juke-box/[id]/programacion/+server.ts +++ b/src/routes/(sitio)/juke-box/[id]/programacion/+server.ts @@ -3,7 +3,12 @@ import { usuarioTieneAccesoJukebox } from '$lib/server/suscripciones'; import { guardarJukebox, leerJukebox, eliminarJukebox } from '$lib/server/jukebox'; import { listasDeUsuario } from '$lib/server/listas-reproduccion'; import { leerCatalogo } from '$lib/server/catalogo'; -import { validarProgramacion, type ProgramacionJukebox } from '$lib/programacion-jukebox'; +import { + validarProgramacion, + usaEtiquetas, + cancionesDeFranja, + type ProgramacionJukebox +} from '$lib/programacion-jukebox'; import type { RequestHandler } from './$types'; const gestionar: RequestHandler = async ({ locals, request, url, params }) => { if (!locals.usuario) return json({ error: 'Entra para acceder a tus jukebox.' }, { status: 401 }); @@ -61,6 +66,11 @@ const gestionar: RequestHandler = async ({ locals, request, url, params }) => { leerCatalogo() ]); for (const [n, franja] of datos.programacion.franjas.entries()) { + if (usaEtiquetas(franja) && !cancionesDeFranja(franja, listas, catalogo.canciones).length) + return json( + { error: `Franja ${n + 1}: no hay canciones con las emociones o etiquetas elegidas.` }, + { status: 400 } + ); for (const fuente of franja.contenidos) { const existe = fuente.tipo === 'lista' diff --git a/src/routes/(sitio)/juke-box/[id]/programacion/jukebox-etiquetas.spec.ts b/src/routes/(sitio)/juke-box/[id]/programacion/jukebox-etiquetas.spec.ts new file mode 100644 index 0000000..a3a7711 --- /dev/null +++ b/src/routes/(sitio)/juke-box/[id]/programacion/jukebox-etiquetas.spec.ts @@ -0,0 +1,81 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type { ProgramacionJukebox } from '$lib/programacion-jukebox'; + +const dobles = vi.hoisted(() => ({ guardar: vi.fn(), catalogo: vi.fn() })); +vi.mock('$lib/server/suscripciones', () => ({ + usuarioTieneAccesoJukebox: vi.fn(async () => true) +})); +vi.mock('$lib/server/jukebox', () => ({ + guardarJukebox: dobles.guardar, + leerJukebox: vi.fn(), + eliminarJukebox: vi.fn() +})); +vi.mock('$lib/server/listas-reproduccion', () => ({ listasDeUsuario: vi.fn(async () => []) })); +vi.mock('$lib/server/catalogo', () => ({ leerCatalogo: dobles.catalogo })); +import { PUT } from './+server'; + +const programa = (etiquetas: string[]): ProgramacionJukebox => ({ + zonaHoraria: 'Europe/Madrid', + franjas: [ + { + id: 'franja', + inicio: '00:00', + fin: '00:00', + orden: 'aleatoria', + catalogo: true, + etiquetas, + contenidos: [] + } + ] +}); +async function guardar(programacion: ProgramacionJukebox) { + const id = '00000000-0000-4000-8000-000000000001'; + const url = new URL(`http://localhost/juke-box/${id}/programacion`); + return PUT({ + locals: { usuario: { id: 'cuenta' } }, + params: { id }, + url, + request: new Request(url, { + method: 'PUT', + headers: { origin: url.origin, 'content-type': 'application/json' }, + body: JSON.stringify({ nombre: 'Emociones', descripcion: '', revision: 1, programacion }) + }) + } as unknown as Parameters[0]); +} +describe('Guardado de franjas por emociones y etiquetas', () => { + beforeEach(() => { + vi.clearAllMocks(); + dobles.catalogo.mockResolvedValue({ canciones: [{ slug: 'una', etiquetas: ['Melancolía'] }] }); + dobles.guardar.mockImplementation(async (_usuario, _id, programacion) => ({ + programacion, + revision: 2 + })); + }); + it('envía las reglas al almacenamiento y las devuelve, sin convertirlas en canciones fijas', async () => { + const p = programa(['MELANCOLIA']); + const r = await guardar(p); + expect(r.status).toBe(200); + expect(await r.json()).toEqual({ programacion: p, revision: 2 }); + expect(dobles.guardar).toHaveBeenCalledWith( + 'cuenta', + expect.any(String), + p, + 1, + 'Emociones', + '' + ); + }); + it('rechaza etiquetas sin coincidencias y no guarda un catálogo completo como sustituto', async () => { + const r = await guardar(programa(['Deseo'])); + expect(r.status).toBe(400); + expect((await r.json()).error).toContain('no hay canciones'); + expect(dobles.guardar).not.toHaveBeenCalled(); + }); + it('rechaza la selección vacía antes de consultar o guardar el catálogo', async () => { + const r = await guardar(programa([])); + expect(r.status).toBe(400); + expect((await r.json()).error).toContain('Elige al menos'); + expect(dobles.catalogo).not.toHaveBeenCalled(); + expect(dobles.guardar).not.toHaveBeenCalled(); + }); +}); diff --git a/src/routes/(sitio)/juke-box/configurar/+page.svelte b/src/routes/(sitio)/juke-box/configurar/+page.svelte index bfbb445..c8ceb05 100644 --- a/src/routes/(sitio)/juke-box/configurar/+page.svelte +++ b/src/routes/(sitio)/juke-box/configurar/+page.svelte @@ -7,6 +7,7 @@ import Meta from '$lib/components/Meta.svelte'; import NavegacionJukebox from '$lib/components/jukebox/NavegacionJukebox.svelte'; import Icono from '$lib/components/Icono.svelte'; + import { usaEtiquetas } from '$lib/programacion-jukebox'; let { data } = $props(); const seleccionado = $derived( @@ -101,7 +102,14 @@ >

- {f.catalogo ? 'Todo el catálogo' : f.pistas.length + ' canciones'} + {usaEtiquetas(f) + ? 'Emociones y etiquetas: ' + f.etiquetas?.join(', ') + : f.catalogo + ? 'Todo el catálogo' + : f.pistas.length + ' canciones'} + {#if usaEtiquetas(f)}{f.pistas.length} canciones{/if} {f.orden === 'aleatoria' ? 'Aleatoria' : 'Secuencial'}

@@ -116,7 +124,8 @@

Tu primer jukebox

- Elige el catálogo completo o tus listas y canciones, y prepara sus franjas de reproducción. + Elige emociones y etiquetas, el catálogo completo o tus listas y canciones, y prepara sus + franjas de reproducción.

Crear jukebox
diff --git a/src/routes/(sitio)/musica/+layout.svelte b/src/routes/(sitio)/musica/+layout.svelte index 9213973..59fbb73 100644 --- a/src/routes/(sitio)/musica/+layout.svelte +++ b/src/routes/(sitio)/musica/+layout.svelte @@ -179,7 +179,7 @@ padding: var(--sp-7) var(--sp-5) var(--sp-4) 0; scrollbar-gutter: stable; scrollbar-width: thin; - scrollbar-color: color-mix(in oklab, var(--c-marca-oro) 82%, transparent) transparent; + scrollbar-color: var(--c-control-oro) transparent; overscroll-behavior: contain; font-size: var(--fs-sm); } @@ -204,9 +204,9 @@ border-radius: var(--radius-round); background: linear-gradient( 180deg, - var(--c-marca-oro-sombra), - var(--c-marca-oro-brillo) 48%, - var(--c-marca-oro) + var(--c-control-oro-sombra), + var(--c-control-oro-brillo) 48%, + var(--c-control-oro) ); } diff --git a/src/routes/(sitio)/musica/+page.svelte b/src/routes/(sitio)/musica/+page.svelte index 8f93b46..4700879 100644 --- a/src/routes/(sitio)/musica/+page.svelte +++ b/src/routes/(sitio)/musica/+page.svelte @@ -60,7 +60,11 @@ album.tipo, String(album.fecha ?? ''), album.resumen, - ...album.canciones.flatMap((cancion) => [cancion.titulo, ...cancion.etiquetas]) + ...album.canciones.flatMap((cancion) => [ + cancion.titulo, + ...cancion.etiquetas, + ...cancion.interpretes.map((interprete) => interprete.nombre) + ]) ].join(' ') ) })) @@ -264,8 +268,7 @@

Música

- Todos los álbumes y canciones del catálogo. Usa los estilos o idiomas de la izquierda y el - desplegable de etiquetas del buscador para acotar la colección. + Álbumes y canciones. Busca por título o filtra por estilo, idioma y etiquetas.

Conocer a los intérpretes →

@@ -455,6 +458,16 @@ } @media (max-width: 42rem) { + .intro { + padding-block: var(--sp-5) var(--sp-2); + } + .intro h1 { + font-size: var(--fs-2xl); + margin-bottom: var(--sp-3); + } + .intro .entradilla-pagina { + font-size: var(--fs-base); + } .cabecera-seccion { align-items: start; flex-direction: column; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 94b074c..e82da0c 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -125,7 +125,7 @@ top: -3rem; z-index: 60; padding: var(--sp-2) var(--sp-4); - background: var(--c-accent); + background: var(--c-accent-superficie); color: var(--c-accent-text); border-radius: var(--radius-boton); transition: top var(--duration-fast) var(--ease-standard); diff --git a/src/tokens.css b/src/tokens.css index 5173f4d..8ff4197 100644 --- a/src/tokens.css +++ b/src/tokens.css @@ -31,6 +31,9 @@ --fs-2xl: 2.125rem; --fs-3xl: 3rem; --fs-4xl: clamp(3.25rem, 9vw, 6rem); + /* Portada: escala con la columna de texto y tiene límites legibles en rem. */ + --fs-hero-title: clamp(2.5rem, min(13cqi, calc(1.25rem + 5vw)), 6rem); + --fs-hero-lead: clamp(var(--fs-base), calc(0.875rem + 0.5cqi), var(--fs-lg)); --fs-editorial-index: clamp(6rem, 15vw, 11rem); --fs-editorial-title: clamp(3.25rem, 6vw, 5.25rem); --fs-editorial-title-compact: clamp(3rem, 14vw, 4rem); @@ -78,6 +81,25 @@ --control-height-compact: 2.25rem; --control-height: 2.75rem; --control-height-touch: 3rem; + /* Roles utilitarios: incluyen línea y peso para no depender del ancestro. */ + --fs-ui-page: 1.75rem; + --fs-ui-section: 1.25rem; + --fs-ui-panel: 1.125rem; + --fs-ui-meta: 0.8125rem; + --ui-page-font: var(--font-weight-semibold) var(--fs-ui-page) / 1.2 var(--font-body); + --ui-section-font: var(--font-weight-semibold) var(--fs-ui-section) / 1.3 var(--font-body); + --ui-panel-font: var(--font-weight-semibold) var(--fs-ui-panel) / 1.35 var(--font-body); + --ui-label-font: var(--font-weight-medium) var(--fs-sm) / 1.4 var(--font-body); + --ui-meta-font: var(--font-weight-regular) var(--fs-ui-meta) / 1.4 var(--font-body); + --ui-input-font: var(--font-weight-regular) var(--fs-base) / 1.5 var(--font-body); + --ui-control-font: var(--font-weight-medium) var(--fs-sm) / 1.25rem var(--font-body); + --ui-control-line: 1.25rem; + --ui-input-padding: calc((var(--control-height) - 1.5rem - 2px) / 2) var(--sp-3); + --ui-action: oklch(0.8 0.095 230); + --ui-action-hover: oklch(0.86 0.075 230); + --ui-action-text: oklch(0.22 0.05 255); + --ui-status-success: oklch(0.78 0.12 155); + --ui-status-draft: oklch(0.8 0.095 230); --icon-size-sm: 1.25rem; --icon-size-md: 1.5rem; --icon-size-lg: 1.75rem; diff --git a/static/images/artistas/cupido-sin-flechas/adrian-vega.webp b/static/images/artistas/cupido-sin-flechas/adrian-vega.webp new file mode 100644 index 0000000..1ee1075 Binary files /dev/null and b/static/images/artistas/cupido-sin-flechas/adrian-vega.webp differ diff --git a/static/images/artistas/cupido-sin-flechas/bruno-leal.webp b/static/images/artistas/cupido-sin-flechas/bruno-leal.webp new file mode 100644 index 0000000..4f7f59e Binary files /dev/null and b/static/images/artistas/cupido-sin-flechas/bruno-leal.webp differ diff --git a/static/images/artistas/cupido-sin-flechas/lia-valdes.webp b/static/images/artistas/cupido-sin-flechas/lia-valdes.webp new file mode 100644 index 0000000..b7b2588 Binary files /dev/null and b/static/images/artistas/cupido-sin-flechas/lia-valdes.webp differ diff --git a/static/images/artistas/cupido-sin-flechas/mara-santel.webp b/static/images/artistas/cupido-sin-flechas/mara-santel.webp new file mode 100644 index 0000000..b29bccc Binary files /dev/null and b/static/images/artistas/cupido-sin-flechas/mara-santel.webp differ
ReproducirPortada TítuloIntérpretes Año Temas Duración
- + {/if}

+

+ {#if interpretes.length > 0} + + {/if} + {#each interpretes as interprete, indice (interprete.id)} + {#if indice > 0}{/if} + {#if interprete.slug} + {interprete.nombre} + {:else} + {interprete.nombre} + {/if} + {:else}Sin acreditar{/each} +

{ano(album.fecha)} · {album.canciones.length} {album.canciones.length === 1 ? 'tema' : 'temas'} @@ -72,6 +84,25 @@ · {duracion}{/if}

+ {#if interpretes.length > 0} + + {/if} + {#each interpretes as interprete, indice (interprete.id)} + {#if indice > 0}{/if} + {#if interprete.slug} + {interprete.nombre} + {:else} + {interprete.nombre} + {/if} + {:else} + Sin acreditar + {/each} + {ano(album.fecha)} {album.canciones.length} {duracion}