Todos los proyectos

Marco — app nativa de hábitos

Seguimiento de hábitos diarios que funciona sin conexión y sin cuenta en ningún servidor: todo vive en un SQLite local del teléfono. App nativa de Android construida sobre Sparkling, el shell de TikTok que empaqueta una app Lynx en un instalable de verdad.

marco-native · Androidjul–ago 2026Ver el repositorio
5.188
líneas de TypeScript
902
líneas de Kotlin
8
gráficas SVG propias
0
servidores implicados
Pantalla de progreso diario de Marco: lista de hábitos del día con un hábito completo, otro a medias y la barra del día en ámbar
Un hábito completo, otro a medias (7,71 de 10) y la barra del día.
Pantalla de hábitos de Marco: cinco hábitos con su categoría y su meta diaria
Alta y edición con categoría, unidad, meta y color libre.
Calendario de Marco: mapa de calor del mes con la escala de cumplimiento
Mapa de calor del mes; tocar un día abre el progreso de esa fecha.
Pantalla de análisis de Marco: comparación de julio contra junio con el desglose por hábito
Julio contra junio: +30 puntos, con el desglose por hábito.

Capturas con datos de ejemplo, no reales: cuenta recién creada con los cinco hábitos que siembra la app y siete semanas de historial generado.

Qué hace

Un toque marca o desmarca un hábito. Dejar pulsada la fila abre el registro parcial —¿leíste 15 de 20 páginas?— mientras una línea del color del hábito recorre el borde de la tarjeta. Los hábitos se borran arrastrando.

El análisis filtra por 7, 14 o 30 días, por mes o por año, compara contra el periodo anterior con sus diferencias y dibuja ocho gráficas SVG con zoom a pantalla completa. Los ajustes cubren categorías, recordatorio diario por notificación, backups a Descargas y borrado de datos.

Cómo está construido

Toda la lógica de negocio vive en src/api/ contra SQLite, un archivo por área —perfil, hábitos, registros, calendario, análisis, categorías, semilla— con marco.ts como barril. El puente src/db/native.ts solo serializa y deserializa: no tiene lógica.

Del lado Android, MarcoDbHelper.kt define el esquema y NativeDatabaseModule.kt ejecuta las consultas. El recordatorio diario son cuatro clases sobre AlarmManagerNativeReminderModule, ReminderScheduler, ReminderAlarmReceiver y BootReceiver— porque sobrevivir a un reinicio del teléfono no es gratis. LynxInputComponent.kt es un <input> propio.

Al crear una cuenta aparecen cinco hábitos de ejemplo con sus categorías, para que ninguna pantalla arranque vacía.

Lo que costó dispositivo

Lynx es un runtime joven y su motor de JavaScript, PrimJS, no es el navegador. Estas siete no se deducen leyendo el código: cada una salió de un fallo real y quedó documentada en su archivo.

  1. Escribir el módulo nativo no basta

    Hay que registrarlo en SparklingApplication.kt vía SparklingLynxConfig.Builder.addLynxModules(...). Sin eso, NativeModules.<Nombre> simplemente no existe del lado JS.

  2. <svg> no es parte del core de Lynx

    Es un X-Element aparte, org.lynxsdk.lynx:xelement-svg, forzado a la versión 3.7.0 en build.gradle.kts y registrado a mano. Las ocho gráficas dependen de ello.

  3. Sin code splitting

    Un import perezoso se compila como bundle Lynx aparte que no hereda el CSS global: texto invisible y pantallas que no se reemplazaban. El router va sin lazy a propósito.

  4. El hilo principal no ve las estructuras

    Dentro de un handler 'main thread' solo llegan los números. Recorrer una tabla declarada arriba del archivo revienta en silencio, sin excepción ni log.

  5. touchstart llega tarde y touchend a veces no llega

    Dentro de un scroll-view el evento se entrega cuando el gesto ya no puede ser scroll; y si se monta un modal encima, la fila que lo abrió nunca recibe el final del gesto. De ahí el componente de deslizamiento propio.

  6. El <input> ignora el color de CSS

    El color del texto va por la prop text-color, no por la hoja de estilos. Centralizado en src/lib/colors.ts para no volver a tropezar.

  7. Sin Intl

    PrimJS no garantiza soporte de internacionalización, así que el formateo de fechas es manual en src/lib/date.ts. Nada de toLocaleDateString.

Lo que falta

  • iOS no está configurado: es una decisión, solo Android por ahora.
  • El identificador del paquete sigue siendo el del scaffold, com.example.sparkling.go.
  • Los backups se exportan como JSON plano, sin cifrar.

Stack

Lynx · PrimJSSparkling (TikTok)TypeScriptKotlinSQLiteAlarmManagervitest

¿Te interesa cómo está resuelto algo de aquí, o tienes un problema parecido?