Files

19 KiB
Raw Permalink Blame History

CLAUDE.md — Guía de desarrollo de Rachita

Este archivo orienta a cualquier agente de IA (Claude, Copilot, etc.) o desarrollador humano que trabaje en este repositorio. Contiene las decisiones de arquitectura ya tomadas, las convenciones a seguir y el estado/plan del proyecto. Léelo antes de escribir código.

El producto y sus requisitos completos están en PRD-Rachita.md. Este documento (CLAUDE.md) es el complemento técnico: cómo se construye, no qué se construye.


1. Resumen del proyecto

Rachita es una app de hábitos y creatividad para tablet Android, dirigida a niñas/niños de 610 años. Mascota virtual + hábitos diarios + rachas + museo de dibujos + sistema de monitoreo de batería (la mascota "se cansa" con poca batería y "duerme" mientras la tablet carga, bloqueando el uso).

Prototipo de referencia visual/UX: Rachita.html (bundle React exportado, no es el código fuente final — úsalo solo como referencia de diseño, animaciones, copy y flujos, ver §10).


2. Elección de plataforma cliente — Flutter vs Kotlin/Compose

Decisión: Kotlin + Jetpack Compose (nativo Android). Justificación abajo — si en el futuro se requiere iPadOS (el PRD lo menciona a nivel aspiracional), se hará como app SwiftUI separada que consume el mismo backend, no un port Flutter.

2.1 Por qué Kotlin nativo gana aquí

Rachita no es una app CRUD — su valor está en integración profunda con el SO Android. La mayoría de los P0 del PRD son APIs nativas que en Flutter obligarían a escribir platform channels en Kotlin de todas formas:

Feature P0 del PRD En Kotlin/Compose En Flutter
Battery Status API (BA-05) — lectura de nivel y estado real, no polling BatteryManager + Intent.ACTION_BATTERY_CHANGED directo Plugin battery_plus o platform channel a Kotlin
Overlay batería baja (BA-01/02) sobre cualquier app WindowManager + TYPE_APPLICATION_OVERLAY nativo Platform channel obligado (Flutter no dibuja fuera de su Activity)
Bloqueo total durante carga (CA-01..05) — pantalla completa sin back Activity full-screen + Screen Pinning / Kiosk API Kotlin nativo obligado
Verificación real del estado ("Ya la desconecté", D-24) Directo con BatteryManager Platform channel
Notificaciones locales (AL-01..04) en lockscreen NotificationManager + canales Plugin flutter_local_notifications (soporta lockscreen pero configuración de canal aún es nativa)
App Widgets 2×2 y 4×2 (WI-01..05) con toque directo sin abrir app AppWidgetProvider + RemoteViews/Glance nativo Flutter no soporta widgets — se hacen 100% en Kotlin con plugin home_widget que solo pasa datos
Servicio en segundo plano (AL-01) Foreground Service nativo Plugin + configuración nativa
Ilustraciones vectoriales por código (mascota) Compose Canvas + drawPath Flutter CustomPainter (paridad)

De ~9 features P0 con dependencia de SO, 7 requieren código Kotlin obligatorio aunque el resto sea Flutter. La ventaja de "un solo codebase" se diluye: quedaría un proyecto Flutter con una capa gruesa de Kotlin, más los bugs de sincronización entre ambas capas (state en Flutter vs. en el servicio Android).

2.2 Cuándo sí elegiría Flutter

Solo si el proyecto fuera:

  • Cross-platform desde v1 con iPadOS como target real (no aspiracional), Y
  • Poco integrado con APIs de sistema (una app de dashboards, listas, formularios).

Ninguna de las dos aplica: v1 es Android tablet only, y la integración con SO es el corazón del producto.

2.3 Sobre iPadOS a futuro

Cuando (si) se haga la versión iPadOS: SwiftUI nativo, misma API .NET compartida. Los widgets WidgetKit y el equivalente iOS del monitoreo de batería/carga tienen APIs muy distintas a Android — un port Flutter no ahorraría tanto como parece.


3. Decisiones de arquitectura (confirmadas)

Decisión Valor
Plataforma cliente Android nativo (Kotlin) — sin iOS en v1 (ver §2)
UI Android Jetpack Compose (Material 3)
Build system Android Gradle con Kotlin DSL (build.gradle.kts) + Version Catalog (libs.versions.toml)
SDK mínimo Android API 26 (Android 8.0) — minSdk = 26; targetSdk = 34
Package name mx.paputec.rachita
Inyección de dependencias Hilt
Persistencia local Room (DAO por feature) + DataStore (preferencias) — offline-first
Networking Retrofit + OkHttp + kotlinx.serialization
Concurrencia Kotlin Coroutines + StateFlow
Ilustración de mascota Vectorial por código con Canvas de Compose (sin assets externos)
Backend ASP.NET Core Web API, .NET 10 (mismo stack que gastosai y pitchmusicadmin)
Arquitectura backend Clean Architecture: Api → Application → Domain ← Infrastructure. REST versionado /api/v1/...
ORM Entity Framework Core (Code First + Migrations)
Base de datos PostgreSQL 16 (Docker postgres:16-alpine, consistente con el resto del servidor)
Autenticación Sin login para la niña. Perfil local + código de familia para "rachas en equipo". Gate parental simple (matemática) para pantallas sensibles
Repositorio Monorepo git.paputec.mx/johannvelazquez/h-bitosInfantiles con /app (Android) y /backend (.NET)
Testing Desde el inicio: JUnit 5 + Compose UI Test en Android, xUnit + FluentAssertions en .NET

3.1 Alcance del primer sprint (MVP real, no todo el PRD)

Construir solo estos 3 módulos primero, de punta a punta (UI + lógica + persistencia local, sin backend aún):

  1. Onboarding / Selección de compañero (Avatar)
  2. Dashboard principal de hábitos y rachas
  3. Sistema de Monitoreo de Batería (overlay batería baja + pantalla de bloqueo en carga)

Los demás módulos del PRD (Museo, Galería, Rachas en equipo, Alertas/notificaciones, Widgets) se abordan en sprints posteriores, en ese orden de prioridad salvo indicación contraria.


4. Infraestructura y despliegue (servidor PapuTec)

El servidor de producción es el mismo que aloja gastosai, pitchmusicadmin y landing-cms. Stack real: Apache 2.4 + Plesk + Docker. NO hay Nginx — cualquier documentación previa que lo mencionara está corregida aquí.

4.1 Topología objetivo del backend

  • Contenedor: rachita-backend (imagen build desde ./backend/Dockerfile, base mcr.microsoft.com/dotnet/aspnet:10.0-alpine).
  • Compose file: /opt/rachita/docker-compose.prod.yml en el servidor, .env con perms 600.
  • Puerto expuesto: 127.0.0.1:8002 (siguiente disponible después de gastosai en 8001; NO exponer 0.0.0.0).
  • DB: rachita-postgres (postgres:16-alpine), volumen rachita_pgdata, red interna rachita_default. Solo accesible dentro de la red del compose.
  • Migraciones: se ejecutan automáticamente al arrancar el contenedor (dotnet ef database update en el entrypoint, patrón de pitchmusicadmin).

4.2 Enrutamiento HTTP — dos opciones (a decidir)

Opción A (recomendada): subdominio custom rachita.paputec.mx — sigue el patrón de finanzas.paputec.mx y git.paputec.mx, todos con vhosts Apache propios en /etc/apache2/sites-enabled/ (fuera de Plesk):

# /etc/apache2/sites-enabled/rachita.paputec.mx.conf (:80 → redirect https + acme-challenge)
# /etc/apache2/sites-enabled/rachita.paputec.mx-le-ssl.conf (:443 → ProxyPass / http://127.0.0.1:8002/)

Cert Let's Encrypt vía certbot certonly --webroot -w /var/www/rachita-acme -d rachita.paputec.mx (NO --apache, sigue patrón finanzas).

Opción B: subruta paputec.mx/rachita/api/ — como paputec.mx/admin/api/ (pitchmusicadmin) o paputec.mx/cms/api/ (landing-cms). Requiere editar ambos vhost.conf y vhost_ssl.conf de paputec.mx bajo /var/www/vhosts/system/paputec.mx/conf/, aplicar con sudo -n plesk sbin httpdmng --reconfigure-domain paputec.mx && sudo -n service apache2 reload.

Para una app móvil, A es la elección natural — la URL base la consume solo el cliente Android, no un browser navegando desde otra ruta. Definir subdominio antes del primer despliegue.

4.3 CI/CD — Gitea Actions

El servidor tiene un act_runner con label android:docker://paputec/android-runner:latest ya disponible (paputec_deployment). Plan:

  • .gitea/workflows/backend.yml — build imagen rachita-backend, push a registry local o docker load directo, docker compose up -d --no-deps rachita-backend contra el host (network host + socket Docker montado en el runner).
  • .gitea/workflows/android.yml — usar el label android, ejecutar ./gradlew testDebugUnitTest lintDebug assembleRelease sobre runs-on: android. Firma de APK con keystore en Gitea Secrets. Publicar APK como artifact de release; instalación en la tablet vía ADB o URL directa (no Play Store en v1).

Trigger: push a main para deploy backend; tag v*.*.* para release APK.

4.4 SSH al servidor

Siempre ssh johann@paputec.mx + sudo -n cuando sea necesario (paputec_ssh). Nunca root. johann no está en el grupo docker — comandos Docker manuales requieren sudo -n docker ....


5. Estructura del repositorio (monorepo)

h-bitosInfantiles/
├── CLAUDE.md                        ← este archivo
├── PRD-Rachita.md                    ← requisitos de producto completos
├── Rachita.html                      ← prototipo visual (referencia, ver §10)
├── README.md
├── STATUS.md                         ← estado vivo (agregar cuando arranque desarrollo)
├── .gitea/workflows/                 ← Gitea Actions (backend.yml, android.yml)
├── app/                              ← proyecto Android (Kotlin + Jetpack Compose)
│   ├── settings.gradle.kts
│   ├── gradle/libs.versions.toml     ← version catalog
│   └── app/
│       ├── build.gradle.kts
│       └── src/main/java/mx/paputec/rachita/
│           ├── ui/                   ← Composables por feature (onboarding, dashboard, battery, museum…)
│           │   └── theme/            ← Color.kt, Type.kt, Shape.kt (§6.1)
│           ├── data/                 ← Room DAOs, DataStore, Retrofit services, DTOs
│           ├── domain/               ← entidades puras + use cases
│           ├── di/                   ← módulos Hilt
│           ├── platform/             ← receivers, services, widgets (código Android específico)
│           └── RachitaApplication.kt
└── backend/                          ← proyecto .NET 10 Web API
    ├── Rachita.sln
    ├── Dockerfile
    ├── docker-compose.dev.yml        ← db + api local
    ├── docker-compose.prod.yml       ← plantilla de despliegue (copiar a /opt/rachita/)
    ├── src/
    │   ├── Rachita.Api/              ← Controllers, Program.cs, middlewares
    │   ├── Rachita.Application/      ← Services, DTOs, interfaces
    │   ├── Rachita.Domain/           ← Entidades y reglas puras
    │   └── Rachita.Infrastructure/   ← EF Core, DbContext, Repositories, Migrations
    └── tests/
        ├── Rachita.Application.Tests/
        └── Rachita.Api.Tests/

Regla dura: el código Android nunca depende directamente de EF Core ni del backend; se comunica solo vía API REST (DTOs propios en el cliente, mapeo explícito).


6. Convenciones de código

6.1 Android / Kotlin

  • Arquitectura por feature dentro de ui/ (ej. ui/onboarding, ui/dashboard, ui/battery), no por tipo de archivo.
  • Patrón MVVM: Screen.kt (Composable) + ViewModel.kt (StateFlow<UiState>) + UiState data class inmutable.
  • DI con Hilt: @HiltAndroidApp, @HiltViewModel, módulos en di/.
  • Nombres: PascalCase para Composables y clases, camelCase para funciones/variables, UPPER_SNAKE_CASE para constantes.
  • Un Composable por archivo cuando sea pantalla completa; sub-componentes pequeños pueden compartir archivo si son private fun.
  • @Preview obligatorio en componentes visuales reutilizables (con estados: default, cargando, error).
  • Colores, tipografías y espaciados centralizados en ui/theme/ siguiendo la guía de estilo del PRD §6 (paleta cream/coral/lila/verde).
  • Animaciones con animateFloatAsState, rememberInfiniteTransition, Animatable — replicar timings del prototipo (PRD §6.4).
  • Código Android específico (BroadcastReceivers, Services, Widgets, WindowManager) va en platform/, no en ui/.

6.2 Backend / .NET

  • Separación estricta de capas: ApiApplicationDomainInfrastructure. Domain no depende de nada; Infrastructure implementa interfaces declaradas en Application.
  • Controllers delgados: request → service → response. Nada de lógica de negocio.
  • Repositorios detrás de interfaces (IHabitRepository, IProfileRepository) definidas en Application.
  • DTOs distintos de las entidades de dominio — nunca exponer entidades EF en la API.
  • Migraciones EF Core con nombre descriptivo: dotnet ef migrations add AddTeamStreaks --project src/Rachita.Infrastructure --startup-project src/Rachita.Api.
  • Endpoints versionados: /api/v1/.... Sustantivos en plural, verbos HTTP correctos (GET /api/v1/habits, POST /api/v1/habits/{id}/complete).
  • Logging: Serilog a stdout (para que Docker/journalctl lo capture). Nunca a archivo dentro del contenedor.
  • Configuración por IOptions<T> + appsettings.json + variables de entorno (ASPNETCORE_ENVIRONMENT=Production). Secretos solo en .env del host, nunca en el repo.

6.3 General

  • Idioma del código (nombres de variables, clases, comentarios técnicos): inglés. Idioma de la UI/copy visible al usuario: español (MX), tal como en el PRD y el prototipo.
  • Commits: mensajes claros en español o inglés consistente por PR. Conventional Commits deseable pero no obligatorio. No agregar Co-Authored-By: Claude (no_coauthor_commits).
  • No crear archivos markdown de resumen/plan salvo que se pida explícitamente.

7. Modelo de datos de referencia

Basado en PRD §9, adaptado a EF Core (backend) y su equivalente Room local (Android):

Profile        (id, childName, avatarKind, customAvatarUri, level, xp, streak, familyCode)
Habit          (id, profileId, name, subtitle, icon, color, active, createdAt)
HabitLog       (habitId, date, done)              -- 1 registro por hábito por día
Artwork        (id, profileId, date, imageUri, title, note, medium)
TeamStreak     (id, ownerProfileId, partnerProfileId, sharedHabitId, streak, week[7])
TeamStreakDay  (teamStreakId, date, myDone, theirDone, nudgedByMe, nudgedByThem)
Settings       (profileId, notifyLowBattery, notifyWhileCharging, habitReminderAt, confettiOn)

Reglas clave a implementar (ver PRD §5.2 y §5.7):

  • La racha global sube +1 solo si todos los hábitos activos del día quedaron done = true antes de medianoche (hora local del dispositivo).
  • En TeamStreak, la racha crece solo si myDone && theirDone el mismo día calendario.
  • Sincronización: cliente autoritativo para HabitLog local hasta que el usuario active "Rachas en equipo"; a partir de ahí el backend reconcilia por (profileId, habitId, date).

8. Sistema de Monitoreo de Batería — notas técnicas

  • Usar Intent.ACTION_BATTERY_CHANGED (sticky broadcast) registrado con registerReceiver para leer nivel y estado de carga en tiempo real — no hacer polling manual.
  • Umbral batería baja: < 20% → mostrar overlay Compose (Dialog o Popup a pantalla completa), no bloqueante, con opción "Ahora no". Reaparición en 15/10/5% (PRD BA-03).
  • Detección de carga: BatteryManager.EXTRA_STATUS == BATTERY_STATUS_CHARGING (o plugged != 0) → activar Activity de bloqueo a pantalla completa, sin back button funcional (PRD CA-01).
  • El botón "Ya la desconecté" debe verificar el estado real del sistema antes de desbloquear (no confiar en el tap) — mitigación de riesgo listada en PRD §13.
  • Evaluar startLockTask() (Screen Pinning, PRD D-24) o modo COSU si la tablet es dedicada, para impedir salir de la app durante la carga.
  • Overlay sobre otras apps (si se requiere fuera de Rachita): WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY + permiso SYSTEM_ALERT_WINDOW. En v1 basta con overlay dentro de la propia app.

9. Cómo ejecutar el proyecto

Android (desarrollo)

cd app
./gradlew assembleDebug
# instalar en dispositivo/emulador conectado:
./gradlew installDebug

Emulador vs tablet física: definir al arrancar el sprint 1. Recomendado tablet física (Lenovo Tab / Samsung Tab de gama media) porque el monitoreo de batería no se comporta igual en emulador.

Backend (desarrollo local)

cd backend
docker compose -f docker-compose.dev.yml up -d  # levanta postgres
dotnet ef database update --project src/Rachita.Infrastructure --startup-project src/Rachita.Api
dotnet run --project src/Rachita.Api
# API en http://localhost:5002

Backend (producción — servidor PapuTec)

# En el servidor, primera vez:
sudo mkdir -p /opt/rachita && sudo chown johann:johann /opt/rachita
scp backend/docker-compose.prod.yml johann@paputec.mx:/opt/rachita/
# crear /opt/rachita/.env con perms 600 (DB_PASSWORD, JWT_SECRET, etc.)

# Deploy (idealmente vía Gitea Actions, ver §4.3):
ssh johann@paputec.mx 'cd /opt/rachita && sudo -n docker compose -f docker-compose.prod.yml pull && sudo -n docker compose -f docker-compose.prod.yml up -d'

10. Uso del prototipo Rachita.html

Es un bundle exportado de una herramienta de diseño (manifiesto base64 + template comprimido), no es código fuente Kotlin/React reutilizable directamente. Úsalo solo para:

  • Extraer valores exactos de la guía de estilo (colores, radios, sombras, timings de animación).
  • Confirmar el copy exacto en español de cada pantalla.
  • Revisar el flujo de estados (showDash, isLow, isCharging, etc.) como referencia de máquina de estados al implementar los ViewModels.

Si necesitas inspeccionar su contenido interno, desempácalo en _extracted/ (carpeta local, agregada a .gitignore, no versionar).


11. Pendientes / decisiones abiertas

Estas preguntas del PRD (§16) siguen sin resolver y pueden afectar el modelo de datos o la UX; confirmarlas antes de implementar el módulo correspondiente:

  1. ¿La racha global se pierde por completo al fallar un día, o hay "congelador" desde v1?
  2. ¿Los hábitos tienen horario límite individual?
  3. ¿Quién etiqueta el "medio" del dibujo (crayón/acuarela/…)?
  4. ¿Reporte/panel para el adulto en v1 o se pospone?
  5. Subdominio de producción — confirmar rachita.paputec.mx (Opción A, §4.2). Bloquea el primer despliegue.
  6. Tablet objetivo — modelo específico para pruebas de batería/kiosk (afecta pruebas de Screen Pinning).
  7. Estrategia exacta de sincronización offline↔backend para hábitos y museo (más allá de "Rachas en equipo").