19 KiB
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 6–10 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):
- Onboarding / Selección de compañero (Avatar)
- Dashboard principal de hábitos y rachas
- 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, basemcr.microsoft.com/dotnet/aspnet:10.0-alpine). - Compose file:
/opt/rachita/docker-compose.prod.ymlen el servidor,.envcon perms 600. - Puerto expuesto:
127.0.0.1:8002(siguiente disponible después de gastosai en 8001; NO exponer0.0.0.0). - DB:
rachita-postgres(postgres:16-alpine), volumenrachita_pgdata, red internarachita_default. Solo accesible dentro de la red del compose. - Migraciones: se ejecutan automáticamente al arrancar el contenedor (
dotnet ef database updateen 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 imagenrachita-backend, push a registry local odocker loaddirecto,docker compose up -d --no-deps rachita-backendcontra el host (network host + socket Docker montado en el runner)..gitea/workflows/android.yml— usar el labelandroid, ejecutar./gradlew testDebugUnitTest lintDebug assembleReleasesobreruns-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>) +UiStatedata class inmutable. - DI con Hilt:
@HiltAndroidApp,@HiltViewModel, módulos endi/. - Nombres:
PascalCasepara Composables y clases,camelCasepara funciones/variables,UPPER_SNAKE_CASEpara constantes. - Un Composable por archivo cuando sea pantalla completa; sub-componentes pequeños pueden compartir archivo si son
private fun. @Previewobligatorio 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 enui/.
6.2 Backend / .NET
- Separación estricta de capas:
Api→Application→Domain←Infrastructure.Domainno depende de nada;Infrastructureimplementa interfaces declaradas enApplication. - Controllers delgados: request → service → response. Nada de lógica de negocio.
- Repositorios detrás de interfaces (
IHabitRepository,IProfileRepository) definidas enApplication. - 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:
Seriloga 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.envdel 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 = trueantes de medianoche (hora local del dispositivo). - En
TeamStreak, la racha crece solo simyDone && theirDoneel mismo día calendario. - Sincronización: cliente autoritativo para
HabitLoglocal 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 conregisterReceiverpara leer nivel y estado de carga en tiempo real — no hacer polling manual. - Umbral batería baja:
< 20%→ mostrar overlay Compose (DialogoPopupa 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(oplugged != 0) → activarActivityde 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+ permisoSYSTEM_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:
- ¿La racha global se pierde por completo al fallar un día, o hay "congelador" desde v1?
- ¿Los hábitos tienen horario límite individual?
- ¿Quién etiqueta el "medio" del dibujo (crayón/acuarela/…)?
- ¿Reporte/panel para el adulto en v1 o se pospone?
- Subdominio de producción — confirmar
rachita.paputec.mx(Opción A, §4.2). Bloquea el primer despliegue. - Tablet objetivo — modelo específico para pruebas de batería/kiosk (afecta pruebas de Screen Pinning).
- Estrategia exacta de sincronización offline↔backend para hábitos y museo (más allá de "Rachas en equipo").