Web de Senza Paura: catálogo, taller, blog y tienda
Sitio del letrista y compositor, con SvelteKit 2 y Svelte 5.
El catálogo es contenido, no base de datos: estilos, álbumes, canciones y
capítulos son Markdown en `src/content/`, validados al compilar y compilados
dentro del bundle, así que recorrer la música no hace ni una consulta. La base
de datos guarda solo lo que generan las personas: cuentas, carritos, pedidos,
compras, valoraciones y favoritos.
Lo que hay:
- Música agrupada por estilos, con página propia para cada estilo, álbum y
tema, buscador y tabla ordenable.
- Reproductor persistente que sobrevive a la navegación. Suena un fragmento;
el archivo completo solo se obtiene comprando.
- Taller de letras: los principios del oficio y una ficha de creación por
estilo hispanoamericano.
- Blog con feed RSS, y etiquetas que cruzan blog y catálogo.
- Tienda con cuentas por código de correo, carrito, Stripe y descargas
protegidas.
- Panel de contenido en `/admin` que escribe los mismos Markdown.
- Tema oscuro de marca y su inversión clara, aplicado antes de pintar.
Lo que falta y no es código: las claves de Stripe y de correo, el tratamiento
del IVA, las páginas de condiciones y privacidad, y el material real. Las
canciones de ejemplo son inventadas y están para sustituirse; el audio son
marcadores salvo un tema.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
|
import fs from 'node:fs';
|
|
|
|
|
import os from 'node:os';
|
|
|
|
|
import path from 'node:path';
|
|
|
|
|
import {
|
|
|
|
|
argumentosDeEscucha,
|
|
|
|
|
aSlug,
|
|
|
|
|
claveCanonica,
|
|
|
|
|
claveDeEscucha,
|
|
|
|
|
comoDuracion,
|
|
|
|
|
enLaBandeja,
|
|
|
|
|
recorrerMasters
|
|
|
|
|
} from './medios.mjs';
|
Web de Senza Paura: catálogo, taller, blog y tienda
Sitio del letrista y compositor, con SvelteKit 2 y Svelte 5.
El catálogo es contenido, no base de datos: estilos, álbumes, canciones y
capítulos son Markdown en `src/content/`, validados al compilar y compilados
dentro del bundle, así que recorrer la música no hace ni una consulta. La base
de datos guarda solo lo que generan las personas: cuentas, carritos, pedidos,
compras, valoraciones y favoritos.
Lo que hay:
- Música agrupada por estilos, con página propia para cada estilo, álbum y
tema, buscador y tabla ordenable.
- Reproductor persistente que sobrevive a la navegación. Suena un fragmento;
el archivo completo solo se obtiene comprando.
- Taller de letras: los principios del oficio y una ficha de creación por
estilo hispanoamericano.
- Blog con feed RSS, y etiquetas que cruzan blog y catálogo.
- Tienda con cuentas por código de correo, carrito, Stripe y descargas
protegidas.
- Panel de contenido en `/admin` que escribe los mismos Markdown.
- Tema oscuro de marca y su inversión clara, aplicado antes de pintar.
Lo que falta y no es código: las claves de Stripe y de correo, el tratamiento
del IVA, las páginas de condiciones y privacidad, y el material real. Las
canciones de ejemplo son inventadas y están para sustituirse; el audio son
marcadores salvo un tema.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
|
|
|
|
|
describe('claveCanonica', () => {
|
|
|
|
|
it('agrupa por disco y numera como la pista', () => {
|
|
|
|
|
const clave = claveCanonica(
|
|
|
|
|
{ album: 'dos-orillas', numero: 3 },
|
|
|
|
|
'media-verdad',
|
|
|
|
|
'subidas/loquesea.mp3',
|
|
|
|
|
''
|
|
|
|
|
);
|
|
|
|
|
expect(clave).toBe('dos-orillas/03-media-verdad.mp3');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('manda a singles lo que no tiene disco', () => {
|
|
|
|
|
// Un tema suelto no ocupa ninguna posición, así que no lleva número.
|
|
|
|
|
const clave = claveCanonica({}, 'dorina', 'subidas/Senza Paura - Dorina.mp3', '');
|
|
|
|
|
expect(clave).toBe('singles/dorina.mp3');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('distingue las alternativas musicales del arreglo principal', () => {
|
|
|
|
|
const clave = claveCanonica(
|
|
|
|
|
{ album: 'como-si-nada', numero: 1 },
|
|
|
|
|
'como-si-nada',
|
|
|
|
|
'subidas/orq.mp3',
|
|
|
|
|
'-orquestal'
|
|
|
|
|
);
|
|
|
|
|
expect(clave).toBe('como-si-nada/01-como-si-nada-orquestal.mp3');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('conserva la extensión, en minúsculas', () => {
|
|
|
|
|
expect(claveCanonica({}, 'dorina', 'subidas/DORINA.WAV', '')).toBe('singles/dorina.wav');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('no numera un single aunque traiga número', () => {
|
|
|
|
|
// El número solo tiene sentido dentro de un disco; el esquema lo ignora,
|
|
|
|
|
// y el nombre del archivo tiene que decir lo mismo.
|
|
|
|
|
expect(claveCanonica({ numero: 4 }, 'dorina', 'subidas/x.mp3', '')).toBe('singles/dorina.mp3');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('enLaBandeja', () => {
|
|
|
|
|
it('reconoce lo que acaba de subir el panel', () => {
|
|
|
|
|
expect(enLaBandeja('subidas/Senza Paura - Dorina.mp3')).toBe(true);
|
|
|
|
|
// El panel puede escribir la clave con barra inicial.
|
|
|
|
|
expect(enLaBandeja('/subidas/dorina.mp3')).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('deja en paz lo que ya está colocado', () => {
|
|
|
|
|
expect(enLaBandeja('singles/dorina.mp3')).toBe(false);
|
|
|
|
|
// Un disco que se llamara así no debe confundirse con la bandeja.
|
|
|
|
|
expect(enLaBandeja('subidas-de-tono/01-tema.mp3')).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('comoDuracion', () => {
|
|
|
|
|
it('escribe m:ss como lo espera el esquema', () => {
|
|
|
|
|
expect(comoDuracion(277.752)).toBe('4:38');
|
|
|
|
|
expect(comoDuracion(65)).toBe('1:05');
|
|
|
|
|
expect(comoDuracion(600)).toBe('10:00');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('aSlug', () => {
|
|
|
|
|
it('coincide con el del esquema', () => {
|
|
|
|
|
expect(aSlug('Vals criollo peruano')).toBe('vals-criollo-peruano');
|
|
|
|
|
expect(aSlug('Versión orquestal')).toBe('version-orquestal');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('escucha pública', () => {
|
|
|
|
|
it('incluye los másteres importados sin Markdown y omite la bandeja', () => {
|
|
|
|
|
const raiz = fs.mkdtempSync(path.join(os.tmpdir(), 'senzapaura-medios-'));
|
|
|
|
|
try {
|
|
|
|
|
fs.mkdirSync(path.join(raiz, 'album'), { recursive: true });
|
|
|
|
|
fs.mkdirSync(path.join(raiz, 'subidas'), { recursive: true });
|
|
|
|
|
fs.writeFileSync(path.join(raiz, 'album', '01-tema.mp3'), 'master');
|
|
|
|
|
fs.writeFileSync(path.join(raiz, 'album', 'nota.txt'), 'no es audio');
|
|
|
|
|
fs.writeFileSync(path.join(raiz, 'subidas', 'pendiente.mp3'), 'pendiente');
|
|
|
|
|
|
|
|
|
|
expect(recorrerMasters(raiz).map((p) => path.relative(raiz, p))).toEqual([
|
|
|
|
|
path.join('album', '01-tema.mp3')
|
|
|
|
|
]);
|
|
|
|
|
} finally {
|
|
|
|
|
fs.rmSync(raiz, { recursive: true, force: true });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('conserva la jerarquía y normaliza el formato', () => {
|
|
|
|
|
expect(claveDeEscucha('disco/01-tema.wav')).toBe('disco/01-tema.mp3');
|
Web de Senza Paura: catálogo, taller, blog y tienda
Sitio del letrista y compositor, con SvelteKit 2 y Svelte 5.
El catálogo es contenido, no base de datos: estilos, álbumes, canciones y
capítulos son Markdown en `src/content/`, validados al compilar y compilados
dentro del bundle, así que recorrer la música no hace ni una consulta. La base
de datos guarda solo lo que generan las personas: cuentas, carritos, pedidos,
compras, valoraciones y favoritos.
Lo que hay:
- Música agrupada por estilos, con página propia para cada estilo, álbum y
tema, buscador y tabla ordenable.
- Reproductor persistente que sobrevive a la navegación. Suena un fragmento;
el archivo completo solo se obtiene comprando.
- Taller de letras: los principios del oficio y una ficha de creación por
estilo hispanoamericano.
- Blog con feed RSS, y etiquetas que cruzan blog y catálogo.
- Tienda con cuentas por código de correo, carrito, Stripe y descargas
protegidas.
- Panel de contenido en `/admin` que escribe los mismos Markdown.
- Tema oscuro de marca y su inversión clara, aplicado antes de pintar.
Lo que falta y no es código: las claves de Stripe y de correo, el tratamiento
del IVA, las páginas de condiciones y privacidad, y el material real. Las
canciones de ejemplo son inventadas y están para sustituirse; el audio son
marcadores salvo un tema.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('codifica la canción completa a menor calidad', () => {
|
|
|
|
|
const args = argumentosDeEscucha(
|
|
|
|
|
'media/audio/singles/dorina.wav',
|
|
|
|
|
'static/escucha/singles/dorina.mp3'
|
|
|
|
|
);
|
|
|
|
|
expect(args).not.toContain('-ss');
|
|
|
|
|
expect(args).not.toContain('-t');
|
|
|
|
|
expect(args).toContain('libmp3lame');
|
|
|
|
|
expect(args).toContain('96k');
|
|
|
|
|
expect(args.at(-1)).toBe('static/escucha/singles/dorina.mp3');
|
Web de Senza Paura: catálogo, taller, blog y tienda
Sitio del letrista y compositor, con SvelteKit 2 y Svelte 5.
El catálogo es contenido, no base de datos: estilos, álbumes, canciones y
capítulos son Markdown en `src/content/`, validados al compilar y compilados
dentro del bundle, así que recorrer la música no hace ni una consulta. La base
de datos guarda solo lo que generan las personas: cuentas, carritos, pedidos,
compras, valoraciones y favoritos.
Lo que hay:
- Música agrupada por estilos, con página propia para cada estilo, álbum y
tema, buscador y tabla ordenable.
- Reproductor persistente que sobrevive a la navegación. Suena un fragmento;
el archivo completo solo se obtiene comprando.
- Taller de letras: los principios del oficio y una ficha de creación por
estilo hispanoamericano.
- Blog con feed RSS, y etiquetas que cruzan blog y catálogo.
- Tienda con cuentas por código de correo, carrito, Stripe y descargas
protegidas.
- Panel de contenido en `/admin` que escribe los mismos Markdown.
- Tema oscuro de marca y su inversión clara, aplicado antes de pintar.
Lo que falta y no es código: las claves de Stripe y de correo, el tratamiento
del IVA, las páginas de condiciones y privacidad, y el material real. Las
canciones de ejemplo son inventadas y están para sustituirse; el audio son
marcadores salvo un tema.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
});
|
|
|
|
|
});
|