Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# CapMark — configuración. La app funciona 100% en local SIN estas variables.
# Rellénalas solo si quieres sincronización multi-dispositivo con tu backend auto-instanciado.
# Genera este archivo como .env con: ./scripts/bootstrap.sh

# --- Backend (Supabase self-hosted; ver infra/docker-compose.yml) ---
VITE_SUPABASE_URL=http://localhost:8000
VITE_SUPABASE_ANON_KEY=

# --- Edge Functions ---
# Verificación de links del lado servidor (RF-017, evita CORS)
VITE_VERIFY_URL=http://localhost:8000/functions/v1/verify-link
# Proxy para el scraper opt-in (descarga el HTML sin CORS)
VITE_SCRAPER_PROXY=http://localhost:8000/functions/v1/fetch-html

# --- Secretos del backend (usados por docker-compose, NO por la app cliente) ---
POSTGRES_PASSWORD=change-me-please
JWT_SECRET=change-me-a-32-char-min-secret-string
ANON_KEY=
SERVICE_ROLE_KEY=
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
build-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- name: Typecheck
run: npm run typecheck
- name: Tests
run: npm test
- name: Build
run: npm run build

backend-smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validar compose y migraciones
run: |
docker compose -f infra/docker-compose.yml config >/dev/null
test -f infra/migrations/0001_init.sql
echo "Compose y migraciones OK"
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
node_modules/
dist/
.env
.env.local
*.log
.DS_Store

# Capacitor
android/
ios/

# Supabase self-hosted volumes
infra/volumes/
50 changes: 50 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# CapMark — guía para trabajar en este repo

Tracker local-first de manga/manhua/novelas (Ionic React + Capacitor + TypeScript).
Backend de sincronización **opcional y auto-instanciable** (Supabase self-hosted por Docker).

## Comandos

- `npm run dev` — app (Vite, http://localhost:5173)
- `npm test` — Vitest (`src/**/*.test.ts`)
- `npm run typecheck` — `tsc --noEmit`
- `npm run build` — typecheck + build
- `npm run bootstrap` — levanta el backend con secretos + migraciones (no manual)
- `make check` — lo que corre CI (typecheck + test + build)

## Arquitectura por capas (respetar RNF-009)

Dependencias **solo hacia adentro**. No romper esta dirección:

```
ui → application → domain
infrastructure (implementa los puertos de application; se inyecta en el container)
```

- `src/domain` — entidades, tipos, reglas puras. **No importa nada** de otras capas.
- `src/application` — casos de uso (`*-service.ts`) y **puertos** (`ports.ts`, interfaces).
No conoce Ionic, Dexie ni Supabase.
- `src/infrastructure` — adaptadores concretos (Dexie, verificador HTTP, scraper, sync) y el
composition root `container.ts` (único sitio que instancia adaptadores).
- `src/ui` — Ionic React. Habla con `container`, nunca con adaptadores directamente.

Aliases de import: `@domain`, `@application`, `@infrastructure`, `@ui`, `@test`.

## Convenciones

- Nombres de dominio en español (Obra, Fuente, Progreso), consistente con `Docs/`.
- Cada regla enlaza su requisito en comentarios (p. ej. `// RF-009`). Ver `Docs/02` y `Docs/03`.
- Lógica nueva con reglas → va en `domain` con su test; orquestación → `application`.
- Persistencia: implementar el puerto `Repositorio`; hoy Dexie (web), SQLite en nativo.
- El **scraper es opt-in y semi-asistido** (decisión de producto, S2): PROPONE, nunca guarda solo.

## Backend

`infra/docker-compose.yml` + `infra/migrations/*.sql` (se aplican al primer arranque) +
`infra/functions/*` (Edge Functions). Secretos en `.env` (generado por `bootstrap.sh`).
La app funciona sin backend; `container.sync.disponible()` decide si hay sincronización.

## Plan

Roadmap por fases en `Docs/06-plan-de-trabajo.md`. Sync completo (outbox + Realtime) = Fase 3.
31 changes: 31 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.PHONY: help install dev build test check bootstrap up down logs

help: ## Muestra esta ayuda
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN{FS=":.*?## "}{printf " \033[36m%-12s\033[0m %s\n", $$1, $$2}'

install: ## Instala dependencias
npm install

dev: ## App en desarrollo (http://localhost:5173)
npm run dev

build: ## Typecheck + build de producción
npm run build

test: ## Tests unitarios
npm test

check: ## Typecheck + tests + build (lo que corre CI)
npm run typecheck && npm test && npm run build

bootstrap: ## Auto-instancia el backend (secretos + compose + migraciones)
./scripts/bootstrap.sh

up: ## Levanta el backend
npm run backend:up

down: ## Detiene el backend
npm run backend:down

logs: ## Sigue los logs del backend
npm run backend:logs
85 changes: 84 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,84 @@
# CapMark-
# CapMark — Gestor de Lecturas

Tracker personal y multiplataforma de **manga, manhua y novelas**. No pierdas el punto de
lectura aunque los sitios cambien de nombre o se caigan: cada obra guarda **varias fuentes**,
su **capítulo actual con historial**, su **estado** y su **prioridad**.

> Es un *tracker*, no un lector: la fuente se abre en el navegador externo. Diseño
> **local-first** — funciona sin conexión y sin backend; la sincronización es opcional.

## Características

- **Catálogo** de obras: título, tipo, nombres alternativos, tags, estado y prioridad.
- **Fuentes por obra** con nombre-en-el-sitio, URL y marca de fuente principal.
- **Progreso e historial** fechado; capítulos con decimales (p. ej. `179.5`).
- **Búsqueda** por título/alias y **filtros** combinables por tag, estado y prioridad.
- **Verificación de fuentes** (opt-in): comprueba si un link responde y señala las caídas.
- **Detección de capítulo** (opt-in, semi-asistida): lee una fuente y **propone** el capítulo;
tú confirmas antes de guardar. Nunca actualiza solo.
- **Backend auto-instanciable**: un comando levanta tu propia sincronización, sin paneles.

## Arranque rápido (app, sin backend)

```bash
npm install
npm run dev # http://localhost:5173
```

La app persiste en el navegador (IndexedDB). Pulsa **"Cargar datos de ejemplo"** para probarla.

## Backend auto-instanciable (opcional, para sincronizar)

No hay que configurar nada a mano. Un comando genera secretos, levanta Postgres + Auth +
Realtime + Edge Functions y **aplica las migraciones solo**:

```bash
./scripts/bootstrap.sh # o: npm run bootstrap
```

Luego rellena en `.env` las claves que imprime el arranque y reinicia `npm run dev`.
Parar: `npm run backend:down`.

Requisitos: Docker (con Compose) y OpenSSL. Ver `infra/` y `.env.example`.

## Móvil (Capacitor)

```bash
npm run build
npx cap add android # y/o: npx cap add ios
npx cap sync
npx cap open android
```

## Arquitectura (separación por capas — RNF-009)

```
src/
domain/ Entidades, tipos e invariantes. Sin dependencias externas.
application/ Casos de uso + puertos (interfaces). No conoce Ionic ni Supabase.
infrastructure/ Adaptadores: Dexie/IndexedDB, verificador, scraper, sync, DI.
ui/ Ionic React (páginas y componentes).
infra/ Backend self-hosted como código (compose, migraciones, functions).
```

El dominio no importa infraestructura: el backend de sync es sustituible sin tocar la lógica.

## Scripts

| Comando | Qué hace |
|---------|----------|
| `npm run dev` | App en desarrollo (Vite) |
| `npm run build` | Typecheck + build de producción |
| `npm test` | Tests unitarios (Vitest) |
| `npm run bootstrap` | Auto-instancia el backend |
| `npm run backend:up` / `:down` / `:logs` | Controla el backend |

## Documentación

Los documentos fundacionales (visión, alcance, requisitos, features, stack y plan de
trabajo) están en [`Docs/`](./Docs).

## Estado

MVP en construcción. La sincronización (outbox + Realtime) es la Fase 3 del plan
(`Docs/06-plan-de-trabajo.md`); el adaptador está preparado como puerto.
12 changes: 12 additions & 0 deletions capacitor.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
appId: 'com.capmark.app',
appName: 'CapMark',
webDir: 'dist',
server: {
androidScheme: 'https',
},
};

export default config;
12 changes: 12 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>CapMark — Gestor de Lecturas</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
102 changes: 102 additions & 0 deletions infra/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Backend auto-instanciable para CapMark (Supabase self-hosted, versión enfocada).
# Un solo comando lo levanta: `cd infra && docker compose up -d`.
# El esquema y las políticas RLS se aplican SOLOS al primer arranque de Postgres
# (todo en ./migrations se ejecuta desde /docker-entrypoint-initdb.d).
#
# Nada de paneles manuales: los secretos vienen del .env de la raíz (ver .env.example).

name: capmark

services:
db:
image: supabase/postgres:15.1.1.78
restart: unless-stopped
ports:
- '5432:5432'
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
POSTGRES_DB: postgres
volumes:
- ./volumes/db:/var/lib/postgresql/data
# Migraciones versionadas: se aplican en orden alfabético en la primera inicialización.
- ./migrations:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ['CMD', 'pg_isready', '-U', 'postgres']
interval: 5s
timeout: 5s
retries: 10

# API REST autogenerada sobre el esquema (PostgREST). Es la API de backup/restore:
# inserta/lee filas respetando RLS con el JWT que emite GoTrue.
rest:
image: postgrest/postgrest:v12.2.3
restart: unless-stopped
ports:
- '3000:3000' # http://localhost:3000/obra , /fuente , /progreso
depends_on:
db:
condition: service_healthy
environment:
PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD:-postgres}@db:5432/postgres
PGRST_DB_SCHEMAS: public
PGRST_DB_ANON_ROLE: anon
PGRST_JWT_SECRET: ${JWT_SECRET:-super-secret-jwt-token-with-at-least-32-chars}

# Autenticación (GoTrue): cuentas y sesiones (RF-015, RNF-006).
auth:
image: supabase/gotrue:v2.158.1
restart: unless-stopped
ports:
- '9999:9999' # http://localhost:9999 (signup/login → JWT)
depends_on:
db:
condition: service_healthy
environment:
GOTRUE_API_HOST: 0.0.0.0
PORT: 9999
API_EXTERNAL_URL: http://localhost:9999
GOTRUE_DB_DRIVER: postgres
GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD:-postgres}@db:5432/postgres
GOTRUE_SITE_URL: http://localhost:5173
GOTRUE_URI_ALLOW_LIST: '*'
GOTRUE_JWT_SECRET: ${JWT_SECRET:-super-secret-jwt-token-with-at-least-32-chars}
GOTRUE_JWT_EXP: 3600
GOTRUE_JWT_AUD: authenticated
GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
GOTRUE_JWT_ADMIN_ROLES: service_role
GOTRUE_DISABLE_SIGNUP: 'false'
# Sin servidor de correo en local: autoconfirma el alta para poder usar la cuenta ya.
GOTRUE_MAILER_AUTOCONFIRM: 'true'
GOTRUE_EXTERNAL_EMAIL_ENABLED: 'true'
GOTRUE_EXTERNAL_PHONE_ENABLED: 'false'

# Sincronización en tiempo real (RNF-005: cambios < 10 s). Solo para la fase de sync en
# vivo (Fase 3); NO es necesario para backup/restore. Requiere config adicional (tenant,
# claves). Arranca solo con: docker compose --profile full up -d
realtime:
image: supabase/realtime:v2.33.58
profiles: ['full']
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DB_HOST: db
DB_PORT: 5432
DB_USER: supabase_admin
DB_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
DB_NAME: postgres
API_JWT_SECRET: ${JWT_SECRET:-super-secret-jwt-token-with-at-least-32-chars}

# Edge Functions (Deno): verificación de links y proxy del scraper (RF-017, RNF-008).
# Requiere un router `main` (falta); no es necesario para backup/restore.
# Arranca solo con: docker compose --profile full up -d
functions:
image: supabase/edge-runtime:v1.66.5
profiles: ['full']
restart: unless-stopped
ports:
- '8000:9000'
volumes:
- ./functions:/home/deno/functions:ro
command: ['start', '--main-service', '/home/deno/functions']
28 changes: 28 additions & 0 deletions infra/functions/fetch-html/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Edge Function: proxy de descarga de HTML para el scraper OPT-IN (evita CORS).
// El extractor de capítulo corre en el cliente sobre el HTML que devuelve esta función.
// GET ?url=https://...

Deno.serve(async (req: Request) => {
const cors = { 'access-control-allow-origin': '*' };
if (req.method === 'OPTIONS') return new Response('ok', { headers: cors });

const target = new URL(req.url).searchParams.get('url');
if (!target || !/^https?:\/\//i.test(target)) {
return new Response('url inválida', { status: 400, headers: cors });
}
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), 8000);
try {
const res = await fetch(target, {
redirect: 'follow',
signal: ctrl.signal,
headers: { 'user-agent': 'Mozilla/5.0 CapMark', accept: 'text/html' },
});
const html = await res.text();
return new Response(html, { headers: { ...cors, 'content-type': 'text/html; charset=utf-8' } });
} catch {
return new Response('', { status: 502, headers: cors });
} finally {
clearTimeout(timer);
}
});
Loading
Loading