Initial commit — Pedrito Hechakuaa v0
Bot de Telegram para monitoreo de notificaciones judiciales del PJ Paraguay. Multi-tenant con onboarding conversacional, audit log, y polling automático. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
295
CLAUDE.md
Normal file
295
CLAUDE.md
Normal file
@@ -0,0 +1,295 @@
|
||||
# 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).
|
||||
|
||||
**Scope estricto de la v0:**
|
||||
- Login al PJ + polling de notificaciones pendientes
|
||||
- Diff vs snapshot anterior → solo notifica lo nuevo
|
||||
- Telegram como canal de salida
|
||||
- Correr en local o VPS con docker-compose
|
||||
|
||||
**Fuera del scope de v0 (no agregar sin consulta explícita):**
|
||||
- Motor de reglas de plazos
|
||||
- LLM / conversación libre
|
||||
- Multi-tenant
|
||||
- Web de onboarding
|
||||
- Actuaciones de expedientes (es v1)
|
||||
- Acuse de notificaciones (nunca en MVP)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 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 |
|
||||
| `OBSERVATIONS.md` | Hallazgos durante el desarrollo (crear si no existe) |
|
||||
| `TECH_DEBT.md` | Deuda técnica anotada (crear si no existe) |
|
||||
| `data/snapshot.json` | Estado persistido del poller |
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user