[{"data":1,"prerenderedAt":484},["ShallowReactive",2],{"sideproject-es-afinador-de-albert-guitarra":3},{"id":4,"title":5,"body":6,"date":440,"description":441,"extension":442,"featured":443,"image":444,"locale":445,"meta":446,"navigation":473,"order":474,"path":475,"repo":476,"seo":477,"status":478,"stem":479,"technologies":480,"url":476,"__hash__":483},"sideprojects\u002Fes\u002Fsideprojects\u002Fafinador-de-albert-guitarra.md","Afinador de Albert (2025)",{"type":7,"value":8,"toc":421},"minimark",[9,13,34,39,42,60,65,89,100,109,118,127,131,161,170,174,178,192,196,206,210,232,236,264,273,277,303,307,339,343,371,375,378,393,396,405],[10,11,5],"h1",{"id":12},"afinador-de-albert-2025",[14,15,16,17,20,22,23,33],"p",{},"Afinador cromático para instrumentos de cuerda con un buscador de acordes por voz integrado. Todo se ejecuta en el navegador: sin backend, sin claves de API, sin datos de audio saliendo del dispositivo.",[18,19],"br",{},[18,21],{},"\nDesplegado como PWA instalable en ",[24,25,26],"strong",{},[27,28,32],"a",{"href":29,"rel":30},"https:\u002F\u002Fafinador.albertsarle.com",[31],"nofollow","afinador.albertsarle.com"," y también empaquetado como aplicación Android nativa (aún no publicada en Google Play).",[35,36,38],"h2",{"id":37},"qué-hace","¿Qué hace?",[14,40,41],{},"Dos cosas:",[43,44,45,53],"ul",{},[46,47,48,52],"li",{},[27,49,51],{"href":50},"#afinador-crom%C3%A1tico","Afinar tu instrumento",": como cualquier app de afinación pero sin anuncios, registro ni nada más.",[46,54,55,59],{},[27,56,58],{"href":57},"#buscador-de-acordes-por-voz","Buscar acordes hablando",": ¿no recuerdas un acorde? Dilo en voz alta y obtén el diagrama de digitación y variantes. Mucho más rápido que buscar en apps de acordes.",[61,62,64],"h3",{"id":63},"afinador-cromático","🎸 Afinador Cromático",[43,66,67,74,77,80,83,86],{},[46,68,69,70,73],{},"Detección de tono en tiempo real usando el ",[24,71,72],{},"algoritmo YIN",", ejecutándose en un Web Worker para que el procesamiento de audio no bloquee la interfaz ni las animaciones.",[46,75,76],{},"Compatible con guitarra (afinaciones estándar y Drop D), bajo y ukelele.",[46,78,79],{},"Dial visual de precisión con indicador por cuerda: toca una cuerda para escuchar su nota de referencia.",[46,81,82],{},"Frecuencia de referencia A4 ajustable entre 432 y 445 Hz.",[46,84,85],{},"Analizador de espectro opcional y modo claro\u002Foscuro.",[46,87,88],{},"Notificación de éxito (sonido + modal) cuando todas las cuerdas están afinadas.",[14,90,91,96],{},[92,93],"img",{"alt":94,"src":95},"Afinador en modo oscuro afinando la cuerda Fa","https:\u002F\u002Fres.cloudinary.com\u002Fdnyvmvkqi\u002Fimage\u002Fupload\u002Fc_scale,f_auto,q_auto,w_700\u002Fv1784021938\u002Fafinador_fa_dark_qgwevu.jpg",[97,98,99],"em",{},"Dial de precisión afinando la cuerda Fa",[14,101,102,106],{},[92,103],{"alt":104,"src":105},"Afinador en modo oscuro afinando la cuerda Re","https:\u002F\u002Fres.cloudinary.com\u002Fdnyvmvkqi\u002Fimage\u002Fupload\u002Fc_scale,f_auto,q_auto,w_700\u002Fv1784021938\u002Fafinador_re_dark_th4gcu.jpg",[97,107,108],{},"El mismo dial respondiendo a la cuerda Re",[14,110,111,115],{},[92,112],{"alt":113,"src":114},"Todas las cuerdas afinadas, modo oscuro","https:\u002F\u002Fres.cloudinary.com\u002Fdnyvmvkqi\u002Fimage\u002Fupload\u002Fc_scale,f_auto,q_auto,w_700\u002Fv1784021938\u002Fafinador_tot_afinat_dark_genjgl.jpg",[97,116,117],{},"Retroalimentación visual cuando todas las cuerdas están afinadas",[14,119,120,124],{},[92,121],{"alt":122,"src":123},"Afinador en modo claro afinando la nota Do","https:\u002F\u002Fres.cloudinary.com\u002Fdnyvmvkqi\u002Fimage\u002Fupload\u002Fc_scale,f_auto,q_auto,w_700\u002Fv1784021938\u002Fafinador_do_light_vbe1bc.jpg",[97,125,126],{},"Mismo afinador en modo claro",[61,128,130],{"id":129},"buscador-de-acordes-por-voz","🎤 Buscador de Acordes por Voz",[43,132,133,136,143,146,154],{},[46,134,135],{},"Botón \"presiona y habla\": di un nombre de acorde en voz alta (p. ej., \"Re menor séptima\" o \"Re menor siete\") para ver el diagrama de digitación de guitarra.",[46,137,138,139,142],{},"Reconoce ",[24,140,141],{},"catalán y español"," en el mismo diccionario de voz.",[46,144,145],{},"Múltiples posiciones por acorde (abierto, cejilla, etc.) que puedes navegar.",[46,147,148,149,153],{},"Historial reciente de acordes, persistente entre sesiones (",[150,151,152],"code",{},"localStorage",").",[46,155,156,157,160],{},"Requiere un navegador con soporte ",[150,158,159],{},"SpeechRecognition"," (Chrome, Edge, Opera, Samsung Internet, Safari 14.1+\u002F14.5+); los navegadores sin soporte como Firefox muestran un aviso claro en lugar de fallar silenciosamente.",[14,162,163,167],{},[92,164],{"alt":165,"src":166},"Diagrama del acorde Si menor séptima, modo oscuro","https:\u002F\u002Fres.cloudinary.com\u002Fdnyvmvkqi\u002Fimage\u002Fupload\u002Fc_scale,f_auto,q_auto,w_700\u002Fv1784021938\u002Facords_si_menor_septima_1_dark_ngb3g4.jpg",[97,168,169],{},"Diagrama de digitación generado al decir \"Si menor séptima\"",[35,171,173],{"id":172},"arquitectura-técnica","Arquitectura Técnica",[61,175,177],{"id":176},"detección-de-tono-yin-en-un-web-worker","Detección de Tono: YIN en un Web Worker",[14,179,180,181,183,184,187,188,191],{},"El núcleo del afinador es una implementación del ",[24,182,72],{}," (autocorrelación con corrección de error acumulado), procesando bloques de 4096 muestras a 44,1 kHz para estimar la frecuencia fundamental del sonido captado por el micrófono. Este cálculo se ejecuta dentro de un ",[24,185,186],{},"Web Worker"," (",[150,189,190],{},"workers\u002FpitchWorker.ts",") para que el análisis de audio, que se ejecuta múltiples veces por segundo, no compita con el hilo principal que dibuja el dial y las animaciones.",[61,193,195],{"id":194},"voz-acorde-parser-lingüístico-personalizado","Voz → Acorde: Parser Lingüístico Personalizado",[14,197,198,201,202,205],{},[150,199,200],{},"services\u002FchordParser.ts"," convierte la transcripción de la Web Speech API en un acorde estructurado (",[150,203,204],{},"{root, quality}",") sin depender de NLP ni servicios externos — es una función pura, testeable sin micrófono. La primera palabra debe ser una nota válida (Do, Re, Mi, Fa, Sol, La, Si), lo que resuelve elegantemente la ambigüedad de ciertos términos. Las frases de cualidad (\"menor séptima\", \"disminuido\"...) se comparan ordenadas por número de palabras, mayor primero, para que \"menor séptima\" se reconozca antes que \"menor\" solo deje \"séptima\" colgada. El mismo diccionario cubre catalán y español simultáneamente.",[61,207,209],{"id":208},"motor-de-teoría-musical-y-base-de-digitaciones","Motor de Teoría Musical y Base de Digitaciones",[14,211,212,215,216,219,220,223,224,231],{},[150,213,214],{},"services\u002FchordTheory.ts"," define 9 cualidades básicas (mayor, menor, 7, m7, maj7, dim, aug, sus2, sus4) como tablas de intervalos y deriva las notas de cada acorde. ",[150,217,218],{},"data\u002FchordFingerings.ts"," es una base de datos estática de ",[24,221,222],{},"108 combinaciones"," (12 tonalidades × 9 cualidades), cada una con una o dos posiciones (abierta y, cuando aplica, cejilla), adaptada de ",[27,225,228],{"href":226,"rel":227},"https:\u002F\u002Fgithub.com\u002Ftombatossals\u002Fchords-db",[31],[150,229,230],{},"@tombatossals\u002Fchords-db",".",[61,233,235],{"id":234},"separación-limpia-de-responsabilidades","Separación Limpia de Responsabilidades",[14,237,238,239,242,243,187,246,249,250,249,253,249,256,259,260,263],{},"El código sigue una división estricta: ",[150,240,241],{},"services\u002F"," contiene solo lógica pura (detección de tono, motor de audio, parser, teoría musical) sin dependencias de React ni DOM, lo que la hace fácil de testear de forma aislada; ",[150,244,245],{},"hooks\u002F",[150,247,248],{},"useTuner",", ",[150,251,252],{},"useVoiceChord",[150,254,255],{},"useModalA11y",[150,257,258],{},"useOnline",") encapsulan el ciclo de vida y estado, reutilizando un patrón de refs para evitar closures obsoletos dentro de listeners registrados una sola vez; ",[150,261,262],{},"components\u002F"," solo renderiza.",[14,265,266,270],{},[92,267],{"alt":268,"src":269},"Panel de configuración, modo oscuro","https:\u002F\u002Fres.cloudinary.com\u002Fdnyvmvkqi\u002Fimage\u002Fupload\u002Fc_scale,f_auto,q_auto,w_700\u002Fv1784021938\u002Fconfiguracio_dark_1_demtw2.jpg",[97,271,272],{},"Configuración: selección de instrumento, frecuencia de referencia y preferencias visuales",[35,274,276],{"id":275},"de-pwa-a-aplicación-android-nativa","De PWA a Aplicación Android Nativa",[14,278,279,280,283,284,287,288,291,292,295,296,299,300,302],{},"La app se construye como una ",[24,281,282],{},"Progressive Web App"," con ",[150,285,286],{},"vite-plugin-pwa"," (manifest, iconos y service worker con actualizaciones automáticas en segundo plano), instalable directamente desde el navegador. Sobre esa base, también se empaqueta como una ",[24,289,290],{},"app Android nativa"," mediante una ",[24,293,294],{},"Trusted Web Activity (TWA)"," generada con Bubblewrap: firmada con su propio keystore, verificada con un archivo ",[150,297,298],{},"assetlinks.json"," que confirma al SO que la app y el dominio ",[150,301,32],{}," pertenecen al mismo propietario, y acompañada de su propia página de política de privacidad (requisito de publicación en Google Play).",[35,304,306],{"id":305},"calidad-accesibilidad-y-resiliencia","Calidad, Accesibilidad y Resiliencia",[43,308,309,315,324],{},[46,310,311,314],{},[24,312,313],{},"Tests (Vitest)",": 5 archivos de test cubren detección de tono, motor de audio, parser de voz y teoría musical — toda la lógica sensible vive en funciones puras fáciles de testear sin mockear el DOM ni el micrófono.",[46,316,317,320,321,323],{},[24,318,319],{},"Accesibilidad",": ",[150,322,255],{}," implementa focus trapping en modales; el botón del micrófono también responde a la barra espaciadora para usuarios con teclado.",[46,325,326,329,330,333,334,336,337,231],{},[24,327,328],{},"Resiliencia",": botón de reintento cuando falla el acceso a audio, mensajes de error en catalán y español sobre códigos ",[150,331,332],{},"DOMException",", detección de conectividad (",[150,335,258],{},") con indicadores en la UI cuando está offline, y un mensaje claro (no fallos silenciosos) en navegadores sin soporte ",[150,338,159],{},[35,340,342],{"id":341},"stack-tecnológico","Stack Tecnológico",[43,344,345,348,351,354,357,360,362,365,368],{},[46,346,347],{},"React 19 & TypeScript",[46,349,350],{},"Vite 6",[46,352,353],{},"Tailwind CSS",[46,355,356],{},"Web Audio API",[46,358,359],{},"Web Speech API",[46,361,286],{},[46,363,364],{},"Vitest",[46,366,367],{},"Docker & Nginx",[46,369,370],{},"TWA \u002F Bubblewrap (Android)",[35,372,374],{"id":373},"despliegue","Despliegue",[14,376,377],{},"Imagen Docker multi-stage, servida con Nginx:",[379,380,381,387],"ol",{},[46,382,383,386],{},[24,384,385],{},"Etapa 1",": construye la app React\u002FVite con Node.",[46,388,389,392],{},[24,390,391],{},"Etapa 2",": copia solo los assets estáticos resultantes y los sirve con Nginx, con headers de caché ajustados para una SPA y service worker.",[394,395],"hr",{},[14,397,398,401,402],{},[24,399,400],{},"Pruébalo tú mismo:"," ",[27,403,29],{"href":29,"rel":404},[31],[14,406,407],{},[97,408,409,410,415,416,231],{},"Digitaciones de acordes adaptadas de ",[27,411,413],{"href":226,"rel":412},[31],[150,414,230],{}," 0.5.1, Copyright (c) 2016 David Rubert, licenciado bajo la ",[27,417,420],{"href":418,"rel":419},"https:\u002F\u002Fgithub.com\u002Ftombatossals\u002Fchords-db\u002Fblob\u002Fmaster\u002FLICENSE",[31],"Licencia MIT",{"title":422,"searchDepth":423,"depth":423,"links":424},"",2,[425,430,436,437,438,439],{"id":37,"depth":423,"text":38,"children":426},[427,429],{"id":63,"depth":428,"text":64},3,{"id":129,"depth":428,"text":130},{"id":172,"depth":423,"text":173,"children":431},[432,433,434,435],{"id":176,"depth":428,"text":177},{"id":194,"depth":428,"text":195},{"id":208,"depth":428,"text":209},{"id":234,"depth":428,"text":235},{"id":275,"depth":423,"text":276},{"id":305,"depth":423,"text":306},{"id":341,"depth":423,"text":342},{"id":373,"depth":423,"text":374},"2026-07-14T12:00:00Z","Afinador cromático para instrumentos de cuerda con buscador de acordes por voz (catalán\u002Fespañol), 100% en el navegador y empaquetado como app Android.","md",false,"https:\u002F\u002Fres.cloudinary.com\u002Fdnyvmvkqi\u002Fimage\u002Fupload\u002Fc_fill,ar_2:1,g_auto,f_auto,q_auto,w_1200\u002Fv1784022134\u002Fdemo_screenshots_pszgvu.png","es",{"slug":447,"type":448,"categories":449,"tags":450,"achievements":454,"metrics":460,"liveUrl":29,"githubUrl":422},"afinador-de-albert-guitarra","sideproject",[448],[451,356,359,282,452,453,353,364],"React & TypeScript","Android (TWA)","Vite",[455,456,457,458,459],"Detección de tono en tiempo real con el algoritmo YIN dentro de un Web Worker","Buscador de acordes por voz en catalán y español (Web Speech API)","Base de datos propia de 108 digitaciones (12 tonalidades × 9 cualidades)","Empaquetada como app Android nativa mediante Trusted Web Activity","Suite de tests (Vitest) para la lógica de audio, voz y teoría musical",[461,464,467,470],{"label":462,"value":463},"Digitaciones de acordes","108",{"label":465,"value":466},"Instrumentos","3",{"label":468,"value":469},"Rango de referencia A4","432–445 Hz",{"label":471,"value":472},"Idiomas de voz","CA \u002F ES",true,1,"\u002Fes\u002Fsideprojects\u002Fafinador-de-albert-guitarra",null,{"title":5,"description":441},"published","es\u002Fsideprojects\u002Fafinador-de-albert-guitarra",[347,350,353,481,359,286,364,367,482],"Web Audio API (detección de tono YIN)","TWA (Bubblewrap) para Android","OatnXwELOcB2nQTqURKlZ8ZZJEUXW_eeB72_j4cmfaE",1790587323143]