Files
hechakuaa_bot/CLAUDE.md
markosbenitez 5eb934d4a1 chore: release v0.3.1 — CHANGELOG + política de versiones en CLAUDE.md
- CHANGELOG.md: historial completo desde v0.1.0 hasta v0.3.1
- CLAUDE.md: sección 'Gestión de versiones' con reglas semver,
  tabla MAJOR/MINOR/PATCH, proceso de release copy-paste
- Scope y archivos del proyecto actualizados al estado real (v0.3.1)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 10:43:59 -03:00

343 lines
12 KiB
Markdown

# Pedrito Hechakuaa — Instrucciones para Claude Code
## Qué es esto
Pedrito Hechakuaa: un bot de Telegram que monitorea notificaciones pendientes
en el sistema judicial de Paraguay (`apps.csj.gov.py`) y avisa cuando aparece
algo nuevo. Uso propio de un solo abogado (single-tenant).
**Versión actual: v0.3.1** — ver historial completo en `CHANGELOG.md`.
**Scope implementado:**
- Login al PJ + polling de notificaciones pendientes (multi-tenant)
- Diff vs snapshot por tenant → solo notifica lo nuevo
- Telegram como canal de salida con onboarding, tono personalizable y timezone
- Búsqueda de expedientes y descarga de PDFs
- Infraestructura: Docker, health check HTTP, schema migrations, webhook opt-in
**Fuera del scope actual (no agregar sin consulta explícita):**
- Motor de reglas de plazos
- LLM / conversación libre
- Web de onboarding
- Actuaciones de expedientes (es v1)
- Acuse de notificaciones (nunca — efecto procesal irreversible)
---
## ⚠️ Invariantes de seguridad — NUNCA se violan, aunque sea v0
```
✅ Credenciales cifradas con Fernet en .env — desencriptar en memoria, usar, descartar
✅ .env en .gitignore desde el primer commit
✅ Todo log pasa por scrub() en utils/sanitize.py antes de escribirse
✅ Audit trail mínimo: quién hizo qué, cuándo, resultado — SIN payload sensible
✅ El bot solo responde al TELEGRAM_CHAT_ID configurado (no es público)
❌ PUT /Notificaciones/{id}/recibir — NUNCA. Acusa notificaciones, efecto procesal irreversible
❌ Password, bearer token, o cualquier credencial en logs (ni en DEBUG)
❌ print() para debug — SIEMPRE usar logger
❌ .env con valores reales en el repo
```
**`scrub()`** (`from utils.sanitize import scrub`): función en `utils/sanitize.py` que recibe cualquier
string y reemplaza patrones sensibles (tokens, credenciales) antes de loggear.
Toda llamada a `logger.*()` con datos externos pasa por acá.
---
## Arquitectura
```
main.py ← entrypoint, arranca bot + poller en paralelo
config.py ← Settings via pydantic-settings (ÚNICA fuente de config)
csj_client.py ← HTTP client del PJ (login + notificaciones, read-only)
poller.py ← loop de polling + diff + dispara alertas
telegram_bot.py ← envía mensajes y maneja /comandos básicos
storage.py ← snapshot en JSON local (sin DB por ahora)
utils/
sanitize.py ← credential_scrubber() — NUNCA loggear sin pasar por acá
```
---
## Stack (sin cambios hasta nuevo aviso)
| Capa | Tecnología |
|------|-----------|
| Lenguaje | Python 3.12 con type hints estrictos |
| HTTP client | `httpx` async |
| Bot | `python-telegram-bot v21` async |
| Scheduler | `APScheduler 3` |
| Cifrado | `cryptography` (Fernet) |
| Config | `pydantic-settings` |
| Logging | `structlog` (JSON en prod, ConsoleRenderer en dev) |
Sin Playwright. Sin Postgres. Sin Redis. Sin LLM. Sin Docker por ahora (opcional).
**No introducir librerías fuera de esta lista sin justificación explícita.**
---
## Sistema judicial target
**URL base**: `https://apps.csj.gov.py`
**Docs**: `docs/reconocimiento/SISTEMA_NUEVO_RECON.md` y `MANUAL_SANITIZADO.md`
### Flujo de autenticación (confirmado por reconocimiento)
```
1. GET https://apps.csj.gov.py/login
→ Cookie: cookiesession1={hex32}
2. POST https://apps.csj.gov.py/autenticador/login
Headers: Cookie: cookiesession1={hex32}
Body JSON: {"usuario": "{cedula}", "clave": "{password}", "temporal": false}
→ bearerToken (TTL 1h), refreshToken, timestampExpiracionUtc
3. Todos los requests siguientes llevan:
Cookie: cookiesession1={hex32}
Authorization: Bearer {bearerToken}
usuario-rol: 16 ← 16 = Abogados
```
### Endpoints de notificaciones (confirmados)
```
GET /api/gestion-partes/Notificaciones/CantidadNotificacionesPorRecibir
→ int (cantidad de notificaciones pendientes)
GET /api/gestion-partes/Notificaciones/PorRecibir?offset=0&cantidad=50
→ lista de NotificacionPendiente
```
### Campos relevantes de NotificacionPendiente
```json
{
"codNotificacionOrigen": 1234567,
"nroExpedienteNumero": 1234,
"nroExpedienteAnio": 2024,
"caratula": "Rodríguez c/ Municipalidad Capital",
"descripcionActuacion": "Auto Interlocutorio",
"descripcionTipoActuacion": "Providencia",
"fecha": "2026-05-20",
"juzgado": "1er Juzgado Civil Capital",
"permisoFirmado": "JWT...",
"codCasoJudicial": 103708,
"origen": 0
}
```
---
## Estados de conversación
El bot es simple pero tiene estados implícitos que deben manejarse explícitamente:
```
UNREGISTERED → usuario no autorizado (responder a cualquier message)
REGISTERED_IDLE → autorizado, sin operación en curso (estado normal)
CHECKING → revisión de notificaciones en curso (no disparar otra)
ERROR_AUTH → fallo de auth del PJ (bot pausado hasta intervención)
ERROR_PASSWORD_TEMP → password temporal detectada (bot pausado)
```
---
## Manejo de errores — mensajes accionables
**Nunca**: "Error inesperado" o mensajes técnicos al usuario.
**Siempre**: qué pasó + qué puede hacer el usuario.
| Situación | Mensaje al abogado |
|-----------|-------------------|
| Login fallido por credenciales | "No pude conectarme al PJ. Las credenciales podrían haber cambiado. Contactá al administrador." |
| Password temporal | "Tu contraseña del PJ es temporal. Cambiala en apps.csj.gov.py y luego reiniciá Pedrito Hechakuaa." |
| Error de red transitorio | "No pude conectarme al sistema judicial. Voy a reintentar en la próxima ronda ({hora})." |
| 5 errores consecutivos | "El sistema del PJ estuvo inaccesible por un tiempo. Si el problema persiste, revisá apps.csj.gov.py directamente." |
Emojis con criterio: ✅ éxito, ❌ error, ⚠️ advertencia, ⏳ procesando. No decorativos.
---
## Estructura del snapshot (storage.py)
JSON local en `data/snapshot.json`:
```json
{
"last_check": "2026-05-20T10:00:00Z",
"notificaciones_vistas": [1234567, 1234568, 1234569]
}
```
Solo guardamos los IDs (`codNotificacionOrigen`) ya notificados.
Diff: cualquier ID en la respuesta del PJ que no esté en esta lista = novedad.
---
## Comandos de Telegram
- `/start` — muestra estado del bot
- `/notif` — revisa notificaciones ahora (no espera al siguiente polling)
- `/estado` — último check, cantidad de notificaciones activas
- `/ayuda` — lista comandos
- `/exp <número>` — detalle de expediente por número (ej: `/exp 198` o `/exp 198/2026`)
- `/exp <texto>` — busca expedientes por carátula (ej: `/exp rodríguez`)
---
## Formato de mensajes al abogado
### Notificación proactiva nueva
```
📋 Nueva notificación judicial
{caratula}
Exp. {numero}/{anio} — {juzgado}
Tipo: {descripcionActuacion}
Fecha: {fecha formateada DD/MM/YYYY}
⚠️ Para acusar esta notificación entrá directamente
en https://apps.csj.gov.py
```
### Sin novedades (solo cuando el abogado lo pide con /notif)
```
✅ Sin novedades desde las {hora} de hoy.
Tenés {N} notificaciones pendientes en total.
```
---
## Iniciativa agéntica — clasificación de cambios
| Nivel | Tipo de cambio | Acción |
|-------|---------------|--------|
| 1 | Refactor interno, naming, código muerto | Ejecutar libremente |
| 2 | Cambio visible en mensajes del bot o UX conversacional | Ejecutar y reportar |
| 3 | Seguridad, credenciales, dependencias nuevas, scope del bot | **Consultar antes** |
**Argumentos NO válidos para evitar consulta de Nivel 3:**
- "Es una mejora de legibilidad"
- "Es trivial de revertir"
- "Resuelve un bug menor"
---
## Definition of Done — cada feature o fix
- [ ] Happy path funciona en ejecución real (`python main.py`)
- [ ] Credenciales no aparecen en logs (verificar con `LOG_LEVEL=DEBUG`)
- [ ] Maneja desconexión del PJ con retry + mensaje claro al usuario
- [ ] Maneja timeout de sesión: relogin automático transparente
- [ ] Maneja datos vacíos (cero notificaciones, response inesperado)
- [ ] `credential_scrubber()` usado en cualquier log con datos externos
- [ ] Type hints completos en funciones nuevas o modificadas
- [ ] Sin `print()` en código nuevo
---
## Alerta especial: fragilidad del cliente HTTP
El `csj_client.py` es la parte más probable de romperse. Ante cualquier reporte
de "el cliente funciona", verificar:
- ¿Se probó con credenciales reales o con mocks?
- ¿Los campos del JSON del PJ son los actuales o los del último test?
- ¿El timeout fue suficiente para la red real?
Ante cambio de schema del PJ: actualizar `csj_client.py` y documentar en
`OBSERVATIONS.md` qué cambió y cuándo.
---
## Archivos del proyecto
| Archivo | Propósito |
|---------|-----------|
| `CLAUDE.md` | Este archivo — reglas para Claude Code |
| `CHANGELOG.md` | Historial de versiones — actualizar antes de cada release |
| `DEPLOY.md` | Guía de despliegue en VPS: Docker, Nginx, SSL, DNS |
| `OBSERVATIONS.md` | Hallazgos durante el desarrollo (schema real del PJ, etc.) |
| `TECH_DEBT.md` | Deuda técnica anotada |
| `migrations/` | Archivos `.sql` de schema — nunca editar los ya aplicados |
| `data/pasante.db` | Base de datos SQLite (tenants, audit_log, invite_codes) |
| `data/snapshot_*.json` | Estado del diff de notificaciones por tenant |
---
## Gestión de versiones
### Esquema: Semantic Versioning (semver 2.0)
`MAJOR.MINOR.PATCH` — tags git con prefijo `v` (ej: `v0.3.1`).
| Número | Incrementar cuando... | Ejemplos |
|--------|----------------------|---------|
| `MAJOR` | Cambio incompatible con versiones anteriores: migración de DB no retrocompatible, cambio de protocolo del PJ, rediseño de arquitectura | `v1.0.0` (migración a Postgres) |
| `MINOR` | Feature nuevo sin romper lo existente: comando nuevo, integración nueva, mejora visible al usuario | `/recordatorio`, modo silencioso |
| `PATCH` | Bug fix, security patch, mejora interna, documentación operacional | corrección de campo del PJ, hardening |
### Reglas que Claude Code sigue siempre
1. **Cada release requiere entrada en `CHANGELOG.md`** con fecha y secciones
`Added / Changed / Fixed / Security` según corresponda.
2. **El tag se crea después del commit de changelog**, no antes.
3. **`[Unreleased]`** acumula todos los cambios hasta que Marcos decide liberar.
4. **Claude propone el número de versión** basándose en las reglas de arriba;
Marcos lo aprueba antes de tagear.
5. **`git tag` y `git push --tags` los ejecuta Claude solo cuando Marcos lo confirma** —
son acciones externas (modifican el remoto) que requieren aprobación explícita.
6. **Tags retroactivos** son válidos para dar historia a commits anteriores;
se crean con `git tag -a vX.Y.Z <hash>`.
### Proceso de release (copy-paste)
```bash
# 1. CHANGELOG.md ya actualizado (mover [Unreleased] → [X.Y.Z] con fecha)
git add CHANGELOG.md
git commit -m "chore: release vX.Y.Z"
# 2. Tag anotado
git tag -a vX.Y.Z -m "vX.Y.Z — resumen en una línea"
# 3. Push incluyendo el tag
git push origin main --tags
```
---
## Cómo arrancar
```bash
# Setup (una sola vez)
pip install uv && uv sync
cp .env.example .env
# Completar .env: FERNET_KEY, TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID
# Cifrar credenciales del PJ
python scripts/cifrar_credenciales.py
# Arrancar
python main.py
# Tests
pytest
pytest -k "poller" -v
pytest --co -q # solo listar tests sin correr
```
---
## Cómo migrar de v0 a v1
La v0 está diseñada para migración incremental:
- `csj_client.py``app/scraper/` con el mismo contrato de métodos
- `storage.py` → Postgres con el mismo interface
- `config.py` → se expande con más settings
- `telegram_bot.py``app/bot/channels/telegram/`
- `poller.py``app/poller/`
- `utils/sanitize.py` → se mantiene igual
No es código desechable.