MyGo 云音乐
Go와 MyGo로 만든 서드파티 네이버 클라우드 뮤직 데스크톱 클라이언트
Inmersia는 책을 이미지·음성·플롯 단서가 포함된 세계로 바꾸어 함께 읽는 커뮤니티와 함께 탐험하게 합니다
Inmersia convierte cada libro en un mundo para habitar: imágenes, sonido, pistas para investigar la trama y una comunidad que lee contigo.
미디어JavaScript · CSSGitHub @JuanValero7온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Plataforma de lectura inmersiva y social
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Plataforma de lectura literaria. Permite a los usuarios gestionar su biblioteca personal, leer libros, tomar notas, explorar una cartelera de personajes y lugares, y participar en foros y chats por libro. Copia el archivo de ejemplo y rellena tus claves de Supabase: Las claves se encuentran en tu proyecto de Supabase → Settings → API. Antes de cualquier commit relevante conviene ejecutar: El script valida tres cosas en secuencia y falla siREADME 전문 보기 ↗
Plataforma de lectura inmersiva en español, desplegada en www.inmersia.io.
Convierte cada libro en un mundo para habitar: ilustraciones, sonido ambiente, un tablero de investigación donde reconstruir la trama, un álbum de barajitas y un foro por libro.
El usuario objetivo es gente que lee poco y quiere leer más. Esa frase decide muchas cosas del código: por qué la accesibilidad importa más de lo normal, por qué el peso del JavaScript se vigila, y por qué la métrica que interesa es la retención y no el número de registros.
Todo el catálogo es de dominio público y el acceso es gratuito.
| Capa | Tecnología |
|---|---|
| Frontend | React 18 + Vite 8 |
| Rutas | react-router-dom 7 |
| Estado de servidor | @tanstack/react-query 5 |
| Backend / Auth / DB | Supabase (Postgres + RLS + Realtime + Storage) |
| Analítica | posthog-js, en modo cookieless |
| Errores | @sentry/browser, cargado solo cuando ocurre un error |
| Estilos | CSS propio por vista + estilos inline |
| Clases condicionales | clsx |
| Hosting | Vercel |
No hay Tailwind (se retiró), ni Redux/Zustand (React Query cubre el estado de servidor), ni TypeScript (ver Convenciones).
Guía completa para quien llega nuevo: Documentation/arquitectura/guia-desarrolladores.md.
COMPONENTES ¿cómo se ve? Lector.jsx, Perfil.jsx, ForoChat.jsx …
────────────────────────────────── ← la frontera
HOOKS ¿qué datos hay? 31 hooks en src/hooks/
SUPABASE dónde viven src/lib/supabase.js
Solo los hooks deberían cruzar esa línea. Cuando un componente llama a supabase
directamente pasan dos cosas: deja de poder probarse sin simular la red entera, y React
Query no se entera de esa escritura, así que su caché se queda con el dato viejo.
Hoy quedan 13 archivos de components/ y context/ que se la saltan. Es deuda conocida,
no un patrón a imitar: se arreglan al tocarlos.
Existe src/components/mobile/ con una versión de casi cada pantalla, más 7 hojas
*.mobile.css. Son alrededor del 25% del código, y es deliberado.
Según el dispositivo hay que ordenar los elementos de forma distinta, no solo redimensionarlos: en escritorio el Lector es un escritorio de doble página con barras flotantes; en móvil es una página a pantalla completa con hojas deslizantes. Eso no se resuelve con media queries.
La regla de qué se bifurca:
| Capa | ¿Se forkea? |
|---|---|
| Wiring de datos (queries, estado de servidor) | Nunca |
| Lógica pura (geometría, formateo, cálculos) | Nunca |
| Primitivas de UI (estrellas, portadas, skeletons) | Nunca — se parametrizan |
| Layout y chrome (orden, hoja vs panel, qué se ve) | Sí, siempre |
El Lector es la plantilla correcta: useLectorData concentra el wiring compartido,
<Notebook> se reutiliza tal cual, y encima hay dos cáscaras distintas.
El objetivo no es cero duplicación. Es que las dos cáscaras contengan solo estructura JSX y ninguna lógica.
Lo decide src/hooks/useIsMobile.js, y es el mismo corte que usa App.jsx para elegir
qué árbol monta. No definas otro corte en un componente suelto: hubo un bug así, con
un useIsMobile local a 640 px, que hacía que entre 641 y 820 px la app se creyera móvil
y la Tienda se creyera escritorio dentro de la misma pantalla.
| Vista | Ruta | Descripción |
|---|---|---|
| Landing | / | Portada pública |
| Auth | pop-up | Login y registro con carnet de acceso |
| Tienda | /tienda | Catálogo público, con fachada de calle y panel de detalle |
| Biblioteca | /biblioteca | Colección personal por categorías, en estanterías |
| Lector | /libro/:slug | Lector paginado con cuaderno, subrayados y predicciones |
| Cartelera | /investigacion/:slug | Tablero: personajes, lugares, hechos, datos y notas |
| Foro | /foro/:slug | Comentarios y chat en tiempo real por libro |
| Álbum | /album | Barajitas coleccionables y estadísticas |
| Perfil | /perfil | Carnet de socio, seguridad, legal y descarga de datos |
Los invitados pueden leer los primeros 10 minutos de cualquier libro: los primeros párrafos
hasta 2300 palabras (parrafos.en_muestra, migración 071). Ese límite lo imponen las políticas
RLS capitulos_guest_preview y parrafos_guest_preview, no la UI.
git clone <url-del-repo>
cd inmersia
npm install
cp .env.example .env.local
| Variable | Obligatoria | Sin ella |
|---|---|---|
VITE_SUPABASE_URL | sí | la app no arranca |
VITE_SUPABASE_ANON_KEY | sí | la app no arranca |
VITE_POSTHOG_KEY | no | la analítica queda dormida |
VITE_POSTHOG_HOST | no | por defecto https://eu.i.posthog.com |
VITE_SENTRY_DSN | no | el reporte de errores queda dormido |
Las claves de Supabase están en tu proyecto → Settings → API.
Las tres opcionales siguen el mismo patrón a propósito: sin la clave, el módulo entero no hace nada, para que un repo clonado o una build de prueba no manden datos a ninguna parte.
npm run dev # servidor de desarrollo
npm run build # vite build + generación de SEO (ver abajo)
npm run preview # previsualizar el build
npm run build necesita las claves de Supabase: el paso de SEO consulta el catálogo. Si
Supabase está caído y hay que desplegar igualmente: SEO_SKIP=1 npm run build.
No hay panel de carga de contenido en la app, y no hace falta construirlo. El autor
tiene dos scripts externos (ficción y no ficción) que procesan un .docx de punta a punta
de forma supervisada y escriben directamente en Supabase.
Esos scripts son hoy la pieza más crítica del proyecto y no están en este repositorio. Conviene versionarlos, aunque sea en un repo privado aparte.
Las migraciones que parecen contenido (002_mock_principito.sql,
044_manual_gato_negro_capitulo.sql…) eran de prueba.
Inmersia es un SPA: vercel.json reescribe todas las rutas a index.html, así que el
<body> llega vacío por el cable. Eso rompía el SEO de forma medible — en septiembre de
2026, Search Console reportaba 50 URLs en «Descubierta: actualmente sin indexar».
scripts/generar-seo.mjs corre después de vite build y, por cada libro visible, escribe
dist/libro/<slug>/index.html con:
<title>, og: y twitter: propias del libroschema.org/Book en el <head><div id="seo-estatico"> relleno con título, portada, sinopsis y el capítulo
1 completoTambién reescribe la home y /tienda con el catálogo como lista de enlaces, para que
exista un grafo de enlaces internos en el HTML crudo.
src/main.jsx retira el bloque estático antes de montar React, así que el usuario solo lo
ve durante la misma ventana en la que antes veía una pantalla en blanco. El JSON-LD va en
el <head> y se queda: sobrevive al renderizado.
Vercel sirve estos archivos porque el sistema de archivos se consulta antes que los rewrites. Un libro sin archivo cae al catch-all de siempre: se degrada solo.
No hay tests de componentes. La red de seguridad son los tests unitarios de la lógica pura y las pruebas de humo: los recorridos que no pueden romperse, en un navegador de verdad.
npm run lint # ESLint: 0 errores y no más hex que el tope (ver abajo)
npm test # 96 tests: paginación, geometría, tokens, edad, anclaje, libros…
npm run humo # recorridos de punta a punta contra el proyecto de PRUEBAS
npm run security-check # lint + escáner de secretos + npm audit
npm run typecheck # JSDoc vía tsc (ver jsconfig.json) — hoy no comprueba nada
npm run humo (scripts/humo.mjs) crea una cuenta desechable, recorre registro, el
invitado que entra desde el muro de la muestra, el límite de pendientes, el lector de
escritorio y de móvil (progreso, volver a la misma página, tira, subrayados y Cuaderno),
la Cartelera, las pantallas principales y borrar la cuenta. Solo corre contra
inmersia-pruebas (se niega con cualquier otra URL) y en tu máquina, no en la CI. Antes de
un cambio que toque el lector, la sesión o la Biblioteca: que salga en verde.
CI. .github/workflows/ci.yml corre lint, tests, build, el escáner de secretos y
npm audit en cada push y cada pull request. No despliega ni bloquea a Vercel: avisa.
Lint. La única regla que queda en aviso es la de colores hex en JSX (~500
heredados). npm run lint topa ese número con --max-warnings: un hex nuevo lo pasa del
tope y falla. Al quitar hex, baja el tope en package.json. Las dependencias de los
hooks de React (exhaustive-deps) son error: si hay que omitir una, va un
eslint-disable-next-line con el motivo escrito encima.
Tipos. typecheck solo revisa los archivos con // @ts-check en la primera línea, y
hoy no lo lleva ninguno, así que no comprueba nada (por eso no está en la CI). Activarlo
solo en src/hooks y src/lib da ~650 errores (medido el 10 oct 2026). El JSDoc de los
hooks sí sirve ya: el editor autocompleta sus props.
| Script | Para qué |
|---|---|
scripts/revisar-pantallas.mjs | Barrido de adaptabilidad en varios anchos, con capturas |
scripts/capturar-layout.mjs | Banco de pruebas de layout sobre probe.html |
scripts/medir-desborde.mjs | Detecta desbordes de layout |
scripts/verificar-csp.mjs | Comprueba que la CSP no rompe nada en producción |
scripts/generar-seo.mjs | Fichas por libro y sitemap.xml (corre en cada build) |
Los dos primeros necesitan .env.revision.local con INMERSIA_EMAIL y
INMERSIA_PASSWORD de una cuenta real: casi todas las pantallas piden sesión.
Varias pantallas cambian entre dos capturas idénticas sin que nadie toque el código:
el tablero de la Cartelera elige los hilos rojos con Math.random, y la Landing y el
Álbum tienen elementos animados. Antes de dar por buena una diferencia de píxeles:
Math.random con addInitScript para fijar la aleatoriedad.Copias de seguridad: el plan gratis de Supabase no las hace. npm run respaldo
(cada noche por tarea programada de Windows) guarda la base y los archivos de Storage en
el disco local. Qué guarda, cómo saber si falla y cómo restaurar:
Documentation/base-de-datos/copias-de-seguridad.md.
77 migraciones en supabase/Migration/, y la seguridad vive en las políticas RLS: la
autorización de superusuario se comprueba en la propia policy
(EXISTS (SELECT 1 FROM superusuarios …)), no solo en la UI.
supabase/Migration/000_politicas_actuales.sql es un retrato con fecha de producción.
No se ejecuta, y no se puede convertir en migración: hay políticas que cambiaron
después y ahí siguen en su versión vieja. Ejecutarlo revertiría, entre otras,
capitulos_select y subrayados_select a USING (true), que abre el catálogo entero y
los subrayados ajenos.
Para una migración ejecutable se usa siempre el BLOQUE 1 de
supabase/exportar-politicas.sql, que lee el catálogo vivo y emite DROP + CREATE
idempotente. Eso no puede quedarse viejo.
| Archivo | Para qué |
|---|---|
supabase/exportar-politicas.sql | Exportar el estado real de RLS al repo |
supabase/consultas/mapa-bd.sql | Genera Documentation/mapa-bd.md (tablas, RLS, Mermaid) |
supabase/consultas/cohortes.sql | Retención por cohortes desde sesiones_lectura |
Todas son de solo lectura y se ejecutan a mano en el SQL Editor de Supabase. No hay script
de Node para esto: pg_class y pg_policies viven en pg_catalog, que PostgREST no
expone — devuelve 404 con cualquier clave.
PostHog está en modo cookieless y su hash de identidad rota cada día, así que un lector
que vuelve cuenta como persona nueva cada jornada. La retención entre días no se puede
medir ahí. Sí en Supabase, porque sesiones_lectura guarda un user_id real y estable.
Reglas que ya costó descubrir una vez. Romperlas no da error: da un fallo silencioso.
Ningún archivo en components/ importa lib/supabase.js. Los datos entran por un
hook. (Excepción viva: Auth.jsx y ResetPassword.jsx usan supabase.auth, que es
autenticación y no datos.)
Ningún hex literal nuevo en .jsx. La paleta está en el bloque :root de
src/index.css. ESLint lo marca como aviso mientras escribes.
clay.jsx repite --ink y --accent como hex a propósito: 116 sitios hacen
`${INK}33` para pegarle un alfa, y eso con var(--ink) produce CSS inválido que
el navegador descarta sin avisar. src/index.tokens.test.js impide que las dos copias
se separen.
El reset del principio de index.css no es decorativo, es estructural. Era el
Preflight de Tailwind. Todo el CSS del proyecto está escrito dando por hecho
box-sizing: border-box, body{margin:0} y h1-h6 sin tamaño propio. Cambiarlo por
otro reset obliga a comparar pantalla por pantalla.
Un solo breakpoint: 820 px, en useIsMobile. No definas otro en un componente.
Desktop y móvil comparten datos y lógica; solo se bifurca el layout. Lo común
tiene casa: hooks/useLectorComun.js (los dos lectores) y
components/biblioteca/miBiblioteca.js (las dos Bibliotecas). Si vas a escribir lo
mismo en los dos archivos, va ahí.
La sesión se lee con useSesion() (context/sesion.jsx): usuario, superusuario y
gato. No vuelvas a pedirla con supabase.auth.getSession() en un hook: una segunda
fuente de verdad ya hizo que el lector no guardara progreso tras entrar.
Una fila de libros se traduce con mapLibro (lib/libros.js). Había siete
copias, con tres colores por defecto distintos.
La geometría de la Cartelera recibe las constantes por parámetro, nunca por clausura.
Los dos tableros usan constantes con el mismo nombre y valores distintos (1180×720 frente
a 860×1000). Si pinAbs leyera MAP por clausura, el tablero móvil se descolocaría
entero sin un solo error en consola.
Toda política RLS nace del export en vivo, nunca copiando el volcado 000.
src/
├── components/
│ ├── Biblioteca.jsx Lector.jsx Tienda.jsx Cartelera.jsx Foro.jsx
│ ├── Album.jsx Perfil.jsx Auth.jsx NoEncontrada.jsx ErrorBoundary.jsx
│ ├── album/ (9) Barajitas, estadísticas, predicciones
│ ├── biblioteca/ (5) + clay/ (7): estanterías, categorías, portadas
│ ├── cartelera/ (12) Tableros, zonas, hilos y fichas
│ ├── foro/ (3) Chat en tiempo real y comentarios
│ ├── landing/ (2) Datos y escena de la portada
│ ├── lector/ (8) Notebook, RecorderPlayer, clay (paleta)
│ ├── legal/ (1) Modal de términos y privacidad
│ ├── onboarding/ (4) Tutorial guiado por fases
│ ├── tienda/ (6) CalleEscena, CatalogoInterior, PanelLibro
│ └── mobile/ (13) Cáscaras móviles + biblioteca/ lector/ tienda/
├── hooks/ (31) La frontera con Supabase
├── lib/ (14) supabase, queries, libros, analytics, errores,
│ carteleraGeometria, edad, misDatos…
├── context/ (4) sesion, authModal, onboarding, pistas
├── styles/ (16) CSS por vista, con su variante .mobile
├── utils/ (3) Paginación del lector (desktop y móvil), helpers
├── index.css Reset base + paleta de marca + utilidades
├── App.jsx Rutas
└── main.jsx Arranque: errores, analítica, render
scripts/ Verificación y generación (ver Verificación)
supabase/
├── Migration/ (77) Esquema y políticas
├── consultas/ SQL de apoyo, solo lectura
└── exportar-politicas.sql
Documentation/ Checklists y notas de trabajo
Cosas pendientes de verdad, para que nadie las descubra otra vez desde cero:
components/ y context/ importan supabase directamente,
saltándose la frontera de hooks.<div>/<span> con onClick sin semántica de control: el tabulador no los
alcanza y un lector de pantalla no los anuncia como pulsables. Pesa más de lo normal
aquí, porque el público objetivo se solapa con quien navega por teclado..jsx, que se van sustituyendo por pantalla..limit() en la query de libros.Documentation/arquitectura/.src/utils/lectorPaginationMobile.js no se refactoriza. Costó mucho estabilizarlo y
sus fallos no explotan: mueven el texto de sitio en silencio. Tiene tests de
caracterización — si fallan, la pregunta es «¿quería cambiar esto?», no «¿qué expectativa
arreglo?».
Sin PropTypes. react/prop-types está desactivada a propósito. Para tipado, JSDoc
con // @ts-check (ver jsconfig.json), no TypeScript.