docs: alinear CLAUDE.md con estándares PapuTec + análisis Flutter vs Kotlin

This commit is contained in:
Johann
2026-07-03 21:04:46 -06:00
parent 35ee014c3c
commit 8d583f6e7e
+186 -85
View File
@@ -10,32 +10,68 @@ Este archivo orienta a cualquier agente de IA (Claude, Copilot, etc.) o desarrol
**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 §9).
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. Decisiones de arquitectura (confirmadas)
## 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 |
| **UI Android** | Jetpack Compose |
| **Build system Android** | Gradle con Kotlin DSL (`build.gradle.kts`) |
| **SDK mínimo Android** | API 26 (Android 8.0) — `minSdk = 26` |
| **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` |
| **Backend** | ASP.NET Core Web API, **.NET 10** |
| **Arquitectura backend** | REST clásico versionado (`/api/v1/...`) con capas **Controller → Service → Repository** |
| **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 |
| **Autenticación** | Sin login para la niña. Perfil local vinculado a un **código de familia** (family code) para emparejar Rachas en equipo. Gate parental simple (no es "auth" real) para pantallas sensibles (Ajustes, invitaciones) |
| **Persistencia local Android** | v1: la app funciona con almacenamiento local simple (DataStore/Room mínimo) para hábitos/museo offline-first; sincronización real con backend se agrega cuando se implemente "Rachas en equipo". Room completo con sync es deseable, no bloqueante |
| **Hosting backend** | Servidor Ubuntu propio, vía **Docker** (contenedor de la API) + **Nginx** como reverse proxy + **systemd** para gestionar el stack |
| **Repositorio** | Monorepo único: `git.paputec.mx/johannvelazquez/h-bitosInfantiles` con carpetas `/app` (Android) y `/backend` (.NET) |
| **CI/CD** | No configurado todavía. Se agrega más adelante (posible runner de build dedicado, ver skill `migrate-ci-build-runner` si aplica) |
| **Testing** | Sí, desde el inicio: JUnit/Compose UI tests en Android, xUnit en .NET |
| **Assets de la mascota** | Se mantiene el enfoque ilustrativo **por código** (formas vectoriales/Compose Canvas), igual que el prototipo HTML — no hay assets de diseño gráfico todavía |
| **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 |
### 2.1 Alcance del primer sprint (MVP real, no todo el PRD)
### 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)**
@@ -46,139 +82,204 @@ Los demás módulos del PRD (Museo, Galería, Rachas en equipo, Alertas/notifica
---
## 3. Estructura del repositorio (monorepo)
## 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, no se ejecuta en prod)
├── 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)
│ ├── build.gradle.kts
│ ├── settings.gradle.kts
│ ├── gradle/libs.versions.toml ← version catalog
│ └── app/
│ ├── build.gradle.kts
│ └── src/main/java/mx/paputec/rachita/
│ ├── ui/ ← pantallas Compose por feature
├── data/ ← repos locales, DataStore/Room, modelos
│ ├── domain/ ← casos de uso / lógica de negocio
── di/inyección de dependencias (Hilt)
│ ├── 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, DI, middlewares
│ ├── Rachita.Api/ ← Controllers, Program.cs, middlewares
│ ├── Rachita.Application/ ← Services, DTOs, interfaces
│ ├── Rachita.Domain/ ← Entidades, reglas de negocio puras
│ ├── Rachita.Domain/ ← Entidades y reglas puras
│ └── Rachita.Infrastructure/ ← EF Core, DbContext, Repositories, Migrations
└── tests/
├── Rachita.Application.Tests/
└── Rachita.Api.Tests/
```
**Regla:** 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).
**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).
---
## 4. Convenciones de código
## 6. Convenciones de código
### 4.1 Android / Kotlin
### 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` de UI state) + `UiState` data class.
- Inyección de dependencias con **Hilt**.
- 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 una pantalla completa; sub-componentes pequeños pueden compartir archivo si son privados (`private fun`).
- Preview de Compose (`@Preview`) obligatorio en componentes visuales reutilizables.
- Colores, tipografías y espaciados centralizados en `ui/theme/` (`Color.kt`, `Type.kt`, `Shape.kt`) siguiendo la guía de estilo del PRD (§6).
- Animaciones con `animateFloatAsState`, `rememberInfiniteTransition`, etc. — replicar timings del prototipo (ver PRD §6.4).
- 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/`.
### 4.2 Backend / .NET
- Separación estricta de capas: `Api` (controllers, DTOs de request/response) → `Application` (servicios, interfaces `IXxxService`) → `Domain` (entidades y reglas puras, sin dependencias externas) → `Infrastructure` (EF Core, `DbContext`, implementaciones de repositorios).
- Controllers **delgados**: solo reciben request, llaman al service, devuelven response. Nada de lógica de negocio en el controller.
- Repositorios detrás de interfaces (`IHabitRepository`, etc.) definidas en `Application` o `Domain`, implementadas en `Infrastructure`.
- DTOs distintos de las entidades de dominio (nunca exponer entidades EF directamente en la API).
- Migraciones EF Core versionadas y con nombre descriptivo (`dotnet ef migrations add AddTeamStreaks`).
- Endpoints versionados: `/api/v1/...`.
- Convención REST: sustantivos en plural, verbos HTTP correctos (`GET /api/v1/habits`, `POST /api/v1/habits/{id}/complete`).
### 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.
### 4.3 General
- Idioma del código (nombres de variables, clases): **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, formato libre (Conventional Commits deseable pero no obligatorio).
- No se necesita documentar cada cambio en archivos markdown nuevos salvo que se pida explícitamente.
### 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.
---
## 5. Modelo de datos de referencia
## 7. Modelo de datos de referencia
Basado en PRD §9, adaptado a EF Core (backend) y su equivalente local (Android):
Basado en PRD §9, adaptado a EF Core (backend) y su equivalente Room local (Android):
```
Profile (childName, avatarKind, customAvatarUri, level, xp, streak, familyCode)
Habit (id, name, subtitle, icon, color, active, createdAt)
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, date, imageUri, title, note, medium)
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.
- 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)`.
---
## 6. Sistema de Monitoreo de Batería — notas técnicas
## 8. Sistema de Monitoreo de Batería — notas técnicas
- Usar `BatteryManager` / `Intent.ACTION_BATTERY_CHANGED` (o `registerReceiver` con `IntentFilter(Intent.ACTION_BATTERY_CHANGED)`) para leer nivel y estado de carga en tiempo real — **no hacer polling manual**.
- Umbral batería baja: `< 20%` → mostrar overlay (Composable con `Dialog` o superposición a pantalla completa), no bloqueante, con opción "Ahora no".
- Detección de carga: `BatteryManager.EXTRA_STATUS == BATTERY_STATUS_CHARGING` (o `plugged != 0`) → activar pantalla de bloqueo total (Activity/Composable a pantalla completa, sin back button funcional, ver PRD CA-01).
- El botón "Ya la desconecté" debe **verificar el estado real** del sistema antes de desbloquear (no confiar ciegamente en el tap del usuario) — mitigación de riesgo listada en PRD §13.
- Evaluar `Screen Pinning` (modo kiosco, PRD D-24) cuando se implemente el bloqueo, para impedir salir de la app durante la carga.
- 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.
---
## 7. Cómo ejecutar el proyecto (a completar conforme se construya)
## 9. Cómo ejecutar el proyecto
### Android
```powershell
### Android (desarrollo)
```bash
cd app
./gradlew assembleDebug
# instalar en dispositivo/emulador conectado:
./gradlew installDebug
```
> Pendiente: confirmar si se usará emulador Android Studio o tablet física para pruebas (no definido aún).
### Backend
```powershell
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
dotnet restore
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
```
### Docker (backend, producción)
```powershell
docker build -t rachita-api ./backend
docker run -d -p 5000:8080 --env-file .env rachita-api
### 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'
```
> Pendiente definir: variables de entorno exactas, configuración de Nginx (dominio/subdominio), certificados TLS y compose file cuando se llegue al sprint de despliegue.
---
## 8. Pendientes / decisiones abiertas
## 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. Dominio/subdominio exacto y configuración de Nginx para el backend en el servidor Ubuntu (pendiente de confirmar con el usuario).
6. Estrategia exacta de sincronización offline↔backend para hábitos y museo (más allá de "Rachas en equipo") aún no definida.
---
## 9. Uso del prototipo `Rachita.html`
Es un bundle exportado de una herramienta de diseño (contiene un 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 el ViewModel de Compose.
Si necesitas inspeccionar su contenido interno, ya fue desempaquetado una vez en `_extracted/` (carpeta temporal, no versionar en git — agregar a `.gitignore` si se conserva).
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").