Files
h-bitosInfantiles/CLAUDE.md
T

286 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](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](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):
```apache
# /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: `Api``Application``Domain``Infrastructure`. `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)
```bash
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)
```bash
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)
```bash
# 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").