From cccc6b5a9336ba19cdbcc1c13e40031f883fae64 Mon Sep 17 00:00:00 2001 From: buenosairesam Date: Wed, 31 Dec 2025 09:07:27 -0300 Subject: [PATCH] spr migrated books, and tester --- CLAUDE.md | 425 ++++---- README.md | 152 ++- artery/__init__.py | 11 + artery/shunts/__init__.py | 18 + artery/shunts/example/README.md | 37 + artery/shunts/example/depot/responses.json | 15 + artery/shunts/example/main.py | 93 ++ atlas/.dockerignore | 56 + atlas/.gitignore | 3 + atlas/CLAUDE.md | 116 +++ atlas/book-template.html | 118 +++ .../arch-model/01-backend-architecture.dot | 184 ++++ .../arch-model/01-backend-architecture.svg | 585 +++++++++++ .../arch-model/02-frontend-architecture.dot | 184 ++++ .../arch-model/02-frontend-architecture.svg | 595 +++++++++++ atlas/book/arch-model/03-data-model.dot | 234 +++++ atlas/book/arch-model/03-data-model.svg | 976 ++++++++++++++++++ .../book/arch-model/04-data-model-simple.dot | 195 ++++ .../book/arch-model/04-data-model-simple.svg | 540 ++++++++++ atlas/book/arch-model/graph.html | 120 +++ atlas/book/arch-model/index.html | 245 +++++ atlas/book/arch-model/styles.css | 565 ++++++++++ atlas/book/drive-index/index.html | 300 ++++++ atlas/book/drive-index/index.md | 217 ++++ atlas/book/feature-flow/CLAUDE.md | 60 ++ atlas/book/feature-flow/index-en.html | 392 +++++++ atlas/book/feature-flow/index-es.html | 470 +++++++++ atlas/book/feature-form-samples/CLAUDE.md | 79 ++ atlas/book/feature-form-samples/detail.html | 335 ++++++ .../feature-form-samples/feature-form/.larder | 0 .../backoffice/01-gestion-solicitudes.md | 92 ++ .../backoffice/02-gestion-usuarios.md | 106 ++ .../backoffice/03-gestion-servicios.md | 98 ++ .../feature-form/backoffice/04-reembolsos.md | 82 ++ .../feature-form/backoffice/05-reportes.md | 99 ++ .../feature-form/pet-owner/01-registro.md | 55 + .../pet-owner/02-reservar-turno.md | 77 ++ .../pet-owner/03-gestion-mascotas.md | 79 ++ .../feature-form/pet-owner/04-pago-turno.md | 66 ++ .../pet-owner/05-historial-medico.md | 79 ++ .../veterinarian/01-aceptar-solicitud.md | 77 ++ .../veterinarian/02-gestion-agenda.md | 87 ++ .../veterinarian/03-realizar-visita.md | 97 ++ .../veterinarian/04-zonas-cobertura.md | 66 ++ .../veterinarian/05-historial-pacientes.md | 81 ++ atlas/book/feature-form-samples/index.html | 143 +++ .../template/feature-form.md | 91 ++ atlas/book/gherkin-samples/CLAUDE.md | 114 ++ atlas/book/gherkin-samples/detail.html | 161 +++ .../backoffice/01-gestion-solicitudes.feature | 104 ++ .../en/backoffice/02-gestion-usuarios.feature | 88 ++ .../backoffice/03-gestion-servicios.feature | 91 ++ .../en/backoffice/04-reembolsos.feature | 86 ++ .../en/backoffice/05-reportes.feature | 97 ++ .../en/pet-owner/01-registro.feature | 91 ++ .../en/pet-owner/02-reservar-turno.feature | 140 +++ .../en/pet-owner/03-gestion-mascotas.feature | 153 +++ .../en/pet-owner/04-pago-turno.feature | 140 +++ .../en/pet-owner/05-historial-medico.feature | 147 +++ .../veterinarian/01-aceptar-solicitud.feature | 109 ++ .../en/veterinarian/02-gestion-agenda.feature | 99 ++ .../veterinarian/03-realizar-visita.feature | 127 +++ .../veterinarian/04-zonas-cobertura.feature | 83 ++ .../05-historial-pacientes.feature | 100 ++ .../backoffice/01-gestion-solicitudes.feature | 158 +++ .../es/backoffice/02-gestion-usuarios.feature | 131 +++ .../backoffice/03-gestion-servicios.feature | 131 +++ .../es/backoffice/04-reembolsos.feature | 141 +++ .../es/backoffice/05-reportes.feature | 155 +++ .../es/pet-owner/01-registro.feature | 92 ++ .../es/pet-owner/02-reservar-turno.feature | 141 +++ .../es/pet-owner/03-gestion-mascotas.feature | 154 +++ .../es/pet-owner/04-pago-turno.feature | 141 +++ .../es/pet-owner/05-historial-medico.feature | 148 +++ .../veterinarian/01-aceptar-solicitud.feature | 129 +++ .../es/veterinarian/02-gestion-agenda.feature | 134 +++ .../veterinarian/03-realizar-visita.feature | 159 +++ .../veterinarian/04-zonas-cobertura.feature | 121 +++ .../05-historial-pacientes.feature | 157 +++ atlas/book/gherkin-samples/index.html | 248 +++++ atlas/index.html | 160 +++ atlas/main.py | 369 +++++++ atlas/requirements.txt | 4 + atlas/static/prism/prism-gherkin.min.js | 1 + atlas/static/prism/prism-line-numbers.min.css | 1 + atlas/static/prism/prism-line-numbers.min.js | 1 + atlas/static/prism/prism-tomorrow.min.css | 1 + atlas/static/prism/prism.min.js | 1 + build.py | 23 + cfg/amar/models/__init__.py | 156 +++ cfg/amar/models/django/models.py | 191 ++++ cfg/amar/models/prisma/schema.prisma | 213 ++++ cfg/amar/models/pydantic/__init__.py | 187 ++++ cfg/amar/models/schema.json | 163 +++ cfg/amar/monitors/turnos/__init__.py | 6 + cfg/amar/monitors/turnos/index.html | 244 +++++ cfg/amar/monitors/turnos/list.html | 188 ++++ cfg/amar/monitors/turnos/main.py | 270 +++++ cfg/amar/tester/tests/README.md | 73 ++ cfg/amar/tester/tests/__init__.py | 2 + cfg/amar/tester/tests/base.py | 164 +++ cfg/amar/tester/tests/conftest.py | 29 + cfg/amar/tester/tests/endpoints.py | 38 + cfg/amar/tester/tests/helpers.py | 44 + .../amar}/tester/tests/mascotas/__init__.py | 0 .../tester/tests/mascotas/test_coverage.py | 0 .../tester/tests/mascotas/test_pet_owners.py | 0 .../amar}/tester/tests/mascotas/test_pets.py | 0 .../amar}/tester/tests/productos/__init__.py | 0 .../amar}/tester/tests/productos/test_cart.py | 0 .../tester/tests/productos/test_categories.py | 0 .../tester/tests/productos/test_services.py | 0 .../tester/tests/solicitudes/__init__.py | 0 .../solicitudes/test_service_requests.py | 0 .../amar}/tester/tests/workflows/__init__.py | 0 .../tests/workflows/test_turnero_general.py | 0 data/__init__.py | 156 +++ data/books.json | 36 +- data/depots.json | 2 +- data/monitors.json | 10 +- data/rooms.json | 8 +- data/{cabinets.json => tables.json} | 0 data/template/feature-form/template.md | 38 + data/templates.json | 2 +- data/tools.json | 29 +- data/veins.json | 6 +- mainroom/CLAUDE.md | 202 ++-- mainroom/amar | 1 + mainroom/ctrl/deploy.sh | 12 +- mainroom/ctrl/start.sh | 6 +- mainroom/sbwrapper/README.md | 22 +- mainroom/sbwrapper/index.html | 14 +- mainroom/sbwrapper/sidebar.css | 20 +- mainroom/sbwrapper/sidebar.js | 40 +- .../tools/tester/tests/example/__init__.py | 1 + .../tools/tester/tests/example/test_health.py | 36 + 136 files changed, 15763 insertions(+), 472 deletions(-) create mode 100644 artery/shunts/__init__.py create mode 100644 artery/shunts/example/README.md create mode 100644 artery/shunts/example/depot/responses.json create mode 100644 artery/shunts/example/main.py create mode 100644 atlas/.dockerignore create mode 100644 atlas/.gitignore create mode 100644 atlas/CLAUDE.md create mode 100644 atlas/book-template.html create mode 100644 atlas/book/arch-model/01-backend-architecture.dot create mode 100644 atlas/book/arch-model/01-backend-architecture.svg create mode 100644 atlas/book/arch-model/02-frontend-architecture.dot create mode 100644 atlas/book/arch-model/02-frontend-architecture.svg create mode 100644 atlas/book/arch-model/03-data-model.dot create mode 100644 atlas/book/arch-model/03-data-model.svg create mode 100644 atlas/book/arch-model/04-data-model-simple.dot create mode 100644 atlas/book/arch-model/04-data-model-simple.svg create mode 100644 atlas/book/arch-model/graph.html create mode 100644 atlas/book/arch-model/index.html create mode 100644 atlas/book/arch-model/styles.css create mode 100644 atlas/book/drive-index/index.html create mode 100644 atlas/book/drive-index/index.md create mode 100644 atlas/book/feature-flow/CLAUDE.md create mode 100644 atlas/book/feature-flow/index-en.html create mode 100644 atlas/book/feature-flow/index-es.html create mode 100644 atlas/book/feature-form-samples/CLAUDE.md create mode 100644 atlas/book/feature-form-samples/detail.html create mode 100644 atlas/book/feature-form-samples/feature-form/.larder create mode 100644 atlas/book/feature-form-samples/feature-form/backoffice/01-gestion-solicitudes.md create mode 100644 atlas/book/feature-form-samples/feature-form/backoffice/02-gestion-usuarios.md create mode 100644 atlas/book/feature-form-samples/feature-form/backoffice/03-gestion-servicios.md create mode 100644 atlas/book/feature-form-samples/feature-form/backoffice/04-reembolsos.md create mode 100644 atlas/book/feature-form-samples/feature-form/backoffice/05-reportes.md create mode 100644 atlas/book/feature-form-samples/feature-form/pet-owner/01-registro.md create mode 100644 atlas/book/feature-form-samples/feature-form/pet-owner/02-reservar-turno.md create mode 100644 atlas/book/feature-form-samples/feature-form/pet-owner/03-gestion-mascotas.md create mode 100644 atlas/book/feature-form-samples/feature-form/pet-owner/04-pago-turno.md create mode 100644 atlas/book/feature-form-samples/feature-form/pet-owner/05-historial-medico.md create mode 100644 atlas/book/feature-form-samples/feature-form/veterinarian/01-aceptar-solicitud.md create mode 100644 atlas/book/feature-form-samples/feature-form/veterinarian/02-gestion-agenda.md create mode 100644 atlas/book/feature-form-samples/feature-form/veterinarian/03-realizar-visita.md create mode 100644 atlas/book/feature-form-samples/feature-form/veterinarian/04-zonas-cobertura.md create mode 100644 atlas/book/feature-form-samples/feature-form/veterinarian/05-historial-pacientes.md create mode 100644 atlas/book/feature-form-samples/index.html create mode 100644 atlas/book/feature-form-samples/template/feature-form.md create mode 100644 atlas/book/gherkin-samples/CLAUDE.md create mode 100644 atlas/book/gherkin-samples/detail.html create mode 100644 atlas/book/gherkin-samples/en/backoffice/01-gestion-solicitudes.feature create mode 100644 atlas/book/gherkin-samples/en/backoffice/02-gestion-usuarios.feature create mode 100644 atlas/book/gherkin-samples/en/backoffice/03-gestion-servicios.feature create mode 100644 atlas/book/gherkin-samples/en/backoffice/04-reembolsos.feature create mode 100644 atlas/book/gherkin-samples/en/backoffice/05-reportes.feature create mode 100644 atlas/book/gherkin-samples/en/pet-owner/01-registro.feature create mode 100644 atlas/book/gherkin-samples/en/pet-owner/02-reservar-turno.feature create mode 100644 atlas/book/gherkin-samples/en/pet-owner/03-gestion-mascotas.feature create mode 100644 atlas/book/gherkin-samples/en/pet-owner/04-pago-turno.feature create mode 100644 atlas/book/gherkin-samples/en/pet-owner/05-historial-medico.feature create mode 100644 atlas/book/gherkin-samples/en/veterinarian/01-aceptar-solicitud.feature create mode 100644 atlas/book/gherkin-samples/en/veterinarian/02-gestion-agenda.feature create mode 100644 atlas/book/gherkin-samples/en/veterinarian/03-realizar-visita.feature create mode 100644 atlas/book/gherkin-samples/en/veterinarian/04-zonas-cobertura.feature create mode 100644 atlas/book/gherkin-samples/en/veterinarian/05-historial-pacientes.feature create mode 100644 atlas/book/gherkin-samples/es/backoffice/01-gestion-solicitudes.feature create mode 100644 atlas/book/gherkin-samples/es/backoffice/02-gestion-usuarios.feature create mode 100644 atlas/book/gherkin-samples/es/backoffice/03-gestion-servicios.feature create mode 100644 atlas/book/gherkin-samples/es/backoffice/04-reembolsos.feature create mode 100644 atlas/book/gherkin-samples/es/backoffice/05-reportes.feature create mode 100644 atlas/book/gherkin-samples/es/pet-owner/01-registro.feature create mode 100644 atlas/book/gherkin-samples/es/pet-owner/02-reservar-turno.feature create mode 100644 atlas/book/gherkin-samples/es/pet-owner/03-gestion-mascotas.feature create mode 100644 atlas/book/gherkin-samples/es/pet-owner/04-pago-turno.feature create mode 100644 atlas/book/gherkin-samples/es/pet-owner/05-historial-medico.feature create mode 100644 atlas/book/gherkin-samples/es/veterinarian/01-aceptar-solicitud.feature create mode 100644 atlas/book/gherkin-samples/es/veterinarian/02-gestion-agenda.feature create mode 100644 atlas/book/gherkin-samples/es/veterinarian/03-realizar-visita.feature create mode 100644 atlas/book/gherkin-samples/es/veterinarian/04-zonas-cobertura.feature create mode 100644 atlas/book/gherkin-samples/es/veterinarian/05-historial-pacientes.feature create mode 100644 atlas/book/gherkin-samples/index.html create mode 100644 atlas/index.html create mode 100644 atlas/main.py create mode 100644 atlas/requirements.txt create mode 100644 atlas/static/prism/prism-gherkin.min.js create mode 100644 atlas/static/prism/prism-line-numbers.min.css create mode 100644 atlas/static/prism/prism-line-numbers.min.js create mode 100644 atlas/static/prism/prism-tomorrow.min.css create mode 100644 atlas/static/prism/prism.min.js create mode 100644 cfg/amar/models/__init__.py create mode 100644 cfg/amar/models/django/models.py create mode 100644 cfg/amar/models/prisma/schema.prisma create mode 100644 cfg/amar/models/pydantic/__init__.py create mode 100644 cfg/amar/models/schema.json create mode 100644 cfg/amar/monitors/turnos/__init__.py create mode 100644 cfg/amar/monitors/turnos/index.html create mode 100644 cfg/amar/monitors/turnos/list.html create mode 100644 cfg/amar/monitors/turnos/main.py create mode 100644 cfg/amar/tester/tests/README.md create mode 100644 cfg/amar/tester/tests/__init__.py create mode 100644 cfg/amar/tester/tests/base.py create mode 100644 cfg/amar/tester/tests/conftest.py create mode 100644 cfg/amar/tester/tests/endpoints.py create mode 100644 cfg/amar/tester/tests/helpers.py rename {station/tools => cfg/amar}/tester/tests/mascotas/__init__.py (100%) rename {station/tools => cfg/amar}/tester/tests/mascotas/test_coverage.py (100%) rename {station/tools => cfg/amar}/tester/tests/mascotas/test_pet_owners.py (100%) rename {station/tools => cfg/amar}/tester/tests/mascotas/test_pets.py (100%) rename {station/tools => cfg/amar}/tester/tests/productos/__init__.py (100%) rename {station/tools => cfg/amar}/tester/tests/productos/test_cart.py (100%) rename {station/tools => cfg/amar}/tester/tests/productos/test_categories.py (100%) rename {station/tools => cfg/amar}/tester/tests/productos/test_services.py (100%) rename {station/tools => cfg/amar}/tester/tests/solicitudes/__init__.py (100%) rename {station/tools => cfg/amar}/tester/tests/solicitudes/test_service_requests.py (100%) rename {station/tools => cfg/amar}/tester/tests/workflows/__init__.py (100%) rename {station/tools => cfg/amar}/tester/tests/workflows/test_turnero_general.py (100%) create mode 100644 data/__init__.py rename data/{cabinets.json => tables.json} (100%) create mode 100644 data/template/feature-form/template.md create mode 120000 mainroom/amar create mode 100644 station/tools/tester/tests/example/__init__.py create mode 100644 station/tools/tester/tests/example/test_health.py diff --git a/CLAUDE.md b/CLAUDE.md index f1f0a28..bc47507 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,67 +15,91 @@ spr/ ├── CLAUDE.md # You are here ├── README.md # User-facing docs ├── schema.json # Source of truth for models -├── cfg/ # Framework configurations -│ ├── soleprint.config.json # Model definitions -│ └── amar/ # Room-specific configs (absolute paths to Dockerfiles) -│ ├── .env.example +├── build.py # Build tool (dev/deploy) +│ +├── cfg/ # Room configurations +│ ├── soleprint.config.json # Framework model definitions +│ └── amar/ # AMAR room config +│ ├── .env │ ├── docker-compose.yml -│ └── link/ # Databrowse adapter for amar +│ ├── Dockerfile.backend +│ ├── Dockerfile.frontend +│ ├── ctrl/ # Room-specific scripts +│ ├── databrowse/depot/ # AMAR schema for databrowse +│ ├── tester/tests/ # AMAR-specific tests +│ ├── monitors/turnos/ # AMAR-specific monitors +│ ├── models/ # AMAR models (pydantic, django, prisma) +│ └── link/ # Databrowse adapter │ -├── ctrl/ # Soleprint room's own ctrl +├── ctrl/ # Soleprint standalone ctrl (Docker) +│ ├── build.sh # python build.py dev --cfg +│ ├── start.sh # docker compose up +│ ├── stop.sh +│ └── logs.sh │ -├── artery/ # VERSIONED - Vital connections -│ ├── veins/ # Single-responsibility connectors +├── artery/ # Vital connections +│ ├── veins/ # Stateless API connectors +│ │ ├── jira/ +│ │ ├── slack/ +│ │ ├── google/ +│ │ ├── base.py +│ │ ├── oauth.py +│ │ └── PATTERNS.md +│ ├── shunts/ # Fake connectors for testing +│ │ └── example/ │ ├── pulses/ # Composed: Vein + Room + Depot -│ ├── room/ # Base room code and ctrl templates -│ └── depots/ # Data storage +│ ├── plexuses/ # Full apps: backend + frontend + DB +│ └── room/ # Base room templates │ -├── atlas/ # VERSIONED - Documentation system -│ ├── templates/ # Gherkin, BDD patterns -│ ├── books/ # Composed: Template + Depot -│ └── depots/ # Data storage -│ -├── station/ # VERSIONED - Tools & execution -│ ├── tools/ # Utilities, generators, runners -│ │ ├── modelgen/ # Model generation from config/codebases -│ │ ├── datagen/ # Test data generation -│ │ ├── tester/ # Test runner (BDD/playwright) -│ │ └── ... -│ ├── desks/ # Composed: Cabinet + Room + Depots -│ ├── rooms/ # Environment configs -│ └── depots/ # Data storage -│ -├── data/ # JSON content files (versioned) -│ -├── soleprint/ # VERSIONED - Core coordinator -│ ├── main.py # Multi-port entry point (production) -│ ├── run.py # Single-port bare-metal dev server -│ ├── index.html # Landing page -│ ├── requirements.txt # Dependencies -│ └── dataloader/ # Data loading module -│ -├── gen/ # RUNNABLE instance (gitignored, copies) +├── atlas/ # Documentation system +│ ├── book/ # Gherkin samples, feature docs, arch diagrams +│ ├── static/ # Prism syntax highlighting │ ├── main.py -│ ├── run.py +│ └── index.html +│ +├── station/ # Tools & execution +│ ├── tools/ +│ │ ├── modelgen/ # Model generation +│ │ ├── datagen/ # Test data generation +│ │ ├── tester/ # BDD/playwright test runner +│ │ ├── graphgen/ # Graph generation +│ │ └── ... +│ └── monitors/ +│ └── databrowse/ # SQL data browser (generic) +│ +├── data/ # JSON content files +│ ├── rooms.json +│ ├── depots.json +│ ├── veins.json +│ └── ... +│ +├── soleprint/ # Core entry points (versioned) +│ ├── main.py # Multi-port entry +│ ├── run.py # Single-port dev server │ ├── index.html │ ├── requirements.txt │ ├── Dockerfile -│ ├── dataloader/ -│ ├── artery/ -│ ├── atlas/ -│ ├── station/ -│ ├── data/ -│ ├── cfg/ -│ └── models/ # Generated by modelgen -│ └── pydantic/ +│ └── dataloader/ │ -└── mainroom/ # Orchestration: soleprint ↔ managed room - ├── ctrl/ # Orchestration commands (acts on mainroom) - ├── sbwrapper/ # Sidebar wrapper UI - └── soleprint/ # Docker configs for soleprint services - ├── docker-compose.yml - ├── docker-compose.nginx.yml # Path-based routing - └── Dockerfile.fastapi +├── gen/ # Built instance (gitignored) +│ ├── ... (copies from soleprint/, artery/, atlas/, station/) +│ ├── models/ # Generated by modelgen +│ └── docker-compose.yml # For standalone Docker +│ +└── mainroom/ # Orchestration: soleprint + managed room + ├── amar -> ../cfg/amar # Symlink to room config + ├── soleprint/ # Soleprint Docker config + │ ├── docker-compose.yml + │ └── docker-compose.nginx.yml + ├── sbwrapper/ # Sidebar wrapper UI + │ ├── config.json + │ ├── sidebar.js + │ └── sidebar.css + └── ctrl/ # Orchestration scripts + ├── start.sh # Start amar + soleprint + ├── stop.sh + ├── deploy.sh # Deploy to AWS + └── server/ # AWS setup scripts ``` ## The Four Systems @@ -87,130 +111,90 @@ spr/ | **Atlas** | Actionable documentation | Mapeando el recorrido | | **Station** | Tools, environments, execution | Centro de control | -## Model Hierarchy +## Artery Hierarchy ``` -Shared: Room (configs), Depot (data) -System-specific: Vein (artery), Template (atlas), Tool (station) -Composed: Pulse (artery), Book (atlas), Desk (station) +Vein ──────► Pulse ──────► Plexus +│ │ │ +│ │ └── Full app: backend + frontend + DB +│ │ (e.g., WhatsApp with chat UI) +│ │ +│ └── Composed: Vein + Room + Depot +│ (e.g., Jira vein for specific project) +│ +└── Stateless API connector + (e.g., Jira, Slack, Google) + + +Shunt ─── Fake connector for testing + (e.g., mercadopago mock with UI to set responses) ``` -**Formulas:** -- Pulse = Vein + Room + Depot -- Book = Template + Depot -- Desk = Cabinet + Room + Depots +| Type | State | Frontend | Deploy | +|------|-------|----------|--------| +| Vein | None (or OAuth) | Optional test UI | With soleprint | +| Pulse | Vein + config | Uses vein's | With soleprint | +| Plexus | Full app state | Required | Self-contained | +| Shunt | Configurable responses | Config UI | With soleprint | ## Key Concepts -### Rooms (Environments) -A **Room** is an environment with soleprint context, features, and conventions: -- Every room has a `ctrl/` folder with commands that act only on that room -- Tools are pluggable into any room -- Managed projects work with their own defaults (env vars set by mainroom ctrl for orchestration) +### Rooms +A **Room** is an environment config with: +- `ctrl/` folder with commands for that room +- `.env` with paths and settings +- Room-specific configs (databrowse depot, tester tests, monitors, models) ### Mainroom -The **mainroom** orchestrates interaction between soleprint and managed rooms: -- `sbwrapper/` - Sidebar UI overlay for any managed app (quick login, Jira info, etc.) -- `soleprint/` - Docker configs for running soleprint services -- `ctrl/` - Mainroom-level orchestration commands (start.sh, stop.sh, etc.) +Orchestrates soleprint + managed room together: +- `mainroom/amar` → symlink to `cfg/amar` +- `mainroom/soleprint/` → soleprint Docker config +- `mainroom/sbwrapper/` → sidebar overlay for quick login, Jira info +- `mainroom/ctrl/` → start/stop/deploy scripts -Soleprint can run without a managed room (for testing veins, etc.). - -### cfg/ - Configuration -- `cfg/soleprint.config.json` - Framework model definitions -- `cfg//` - Room-specific configs (e.g., `cfg/amar/`) - - Uses absolute paths to external Dockerfiles - - Room-specific tools/adapters (e.g., databrowse link adapter) - -### soleprint/ vs gen/ -- `soleprint/` = Versioned core files (main.py, run.py, dataloader, index.html) -- `gen/` = Gitignored runnable instance (copies, not symlinks - Docker compatible) -- `gen/models/` = Generated models - -**Development:** Edit source → `python build.py dev` → run from gen/ - -### Modelgen (Generic Tool) -Lives in `station/tools/modelgen/`. It: -1. Reads `cfg/soleprint.config.json` (source of truth) -2. Generates Pydantic models to `gen/models/` -3. Generation is **one-time per client** (like install) -4. Runs standalone (no model dependencies) for bootstrap +### Build & Gen +- `soleprint/` = Versioned source +- `gen/` = Built instance (gitignored, Docker-ready) +- `python build.py dev --cfg amar` copies everything + room config ## Development Workflow -### Build Tool - -The build script at spr root handles both development and deployment builds: - -```bash -# From spr/ -python build.py dev # Build gen/ from source (copies) -python build.py dev --cfg amar # Include amar room config -python build.py deploy --output /path/ # Build for production -python build.py models # Only regenerate models -``` - -### Setting Up Dev Environment +### Soleprint Standalone (no managed room) ```bash +# Build cd spr/ -python build.py dev # Creates gen/ with symlinks +python build.py dev -cd gen/ -python3 -m venv .venv -.venv/bin/pip install -r requirements.txt -.venv/bin/python run.py # Single-port bare-metal dev server -# or -.venv/bin/python main.py # Multi-port (production-like) +# Run with Docker +./ctrl/start.sh + +# Or bare-metal +cd gen && .venv/bin/python run.py ``` -### Bare-metal vs Docker -- **Bare-metal:** `python run.py` - Single port, all routes internal, for soleprint dev -- **Docker:** Use mainroom for managed rooms - separate containers, nginx routing - -### Building for Deployment +### Soleprint + Amar (with managed room) ```bash +# Build soleprint with amar config cd spr/ -python build.py deploy --output ../deploy/soleprint/ --cfg amar +python build.py dev --cfg amar -# Then deploy: -rsync -av ../deploy/soleprint/ server:/app/soleprint/ -ssh server 'cd /app/soleprint && ./start.sh' +# Create shared network +docker network create soleprint_network -# Or use mainroom ctrl scripts: -cd mainroom/soleprint/ctrl/local -./deploy.sh +# Start everything +cd mainroom/ctrl +./start.sh -d # Detached +./start.sh # Foreground (logs) +./stop.sh # Stop all ``` -### Orchestrating with Managed Room +### Deploy to AWS ```bash -cd spr/mainroom/ctrl -./start.sh # Sets env vars, starts soleprint + managed room services +cd mainroom/ctrl +./deploy.sh --dry-run # Preview +./deploy.sh # Deploy ``` -### Worktrees -Feature development in: `/home/mariano/wdir/wts/spr/` - -Planned: -- `databrowse` - Data browser tool (uses modelgen extract) -- `sbwrapper` - Sidebar wrapper development - -## External References - -| What | Location | Notes | -|------|----------|-------| -| Amar Backend | `ama/amar_django_back` | Example managed room | -| Amar Frontend | `ama/amar_frontend` | Example managed room | - -## Tools Status - -| Tool | Location | Status | Notes | -|------|----------|--------|-------| -| modelgen | station/tools/modelgen | Working | Generic model generation (used by build.py, databrowse) | -| datagen | station/tools/datagen | Working | Test data generation | -| tester | station/tools/tester | Advanced | Full BDD/playwright | -| infra | station/tools/infra | Idea | Cloud deploy scripts | -| graphgen | station/tools/graphgen | Idea | Graph generation | - ## Ports | Service | Port | @@ -219,60 +203,119 @@ Planned: | Artery | 12001 | | Atlas | 12002 | | Station | 12003 | +| Amar Backend | 8000 | +| Amar Frontend | 3000 | -## Current State +## Tools -**Done:** -- [x] Project structure finalized -- [x] Schema.json in place -- [x] Modelgen in station/tools/ -- [x] soleprint/gen separation with symlinks -- [x] Mainroom structure -- [x] Docker configs with nginx path-based routing -- [x] Build tool with dev/deploy modes and --cfg argument -- [x] Bare-metal run.py for single-port dev -- [x] cfg/amar/ with absolute paths pattern -- [x] Renamed: hub→soleprint, config→cfg, nest→room, pawprint→soleprint +| Tool | Location | Status | +|------|----------|--------| +| modelgen | station/tools/modelgen | Working | +| datagen | station/tools/datagen | Working | +| tester | station/tools/tester | Advanced | +| graphgen | station/tools/graphgen | WIP | -**Next:** -1. [ ] Test mainroom/soleprint/ctrl scripts -2. [ ] Test mainroom with managed room (amar) -3. [ ] Worktree for databrowse (uses modelgen extract) -4. [ ] Worktree for sbwrapper +## Monitors -## Files Ignored (gitignore) +| Monitor | Location | Notes | +|---------|----------|-------| +| databrowse | station/monitors/databrowse | Generic SQL browser | +| turnos | cfg/amar/monitors/turnos | AMAR-specific | -- `fails/` - Previous attempts, reference only -- `def/` - Definition drafts -- `gen/` - Entire folder gitignored (regenerate with `python build.py dev`) -- `__pycache__/`, `*.pyc` -- `venv/`, `.venv/` +## Veins + +| Vein | Location | Auth | +|------|----------|------| +| jira | artery/veins/jira | Token | +| slack | artery/veins/slack | Token | +| google | artery/veins/google | OAuth2 | ## Quick Reference ```bash -# Build for dev (from spr/) -python build.py dev -python build.py dev --cfg amar # With amar room config +# === Build === +python build.py dev # Soleprint only +python build.py dev --cfg amar # With amar config +python build.py deploy --output /path/ # Production build -# Start dev server (bare-metal, single-port) -cd gen && .venv/bin/python run.py +# === Standalone (soleprint only) === +./ctrl/build.sh amar # Build with amar +./ctrl/start.sh # Docker start +./ctrl/stop.sh # Docker stop -# Start production-like (multi-port) -cd gen && .venv/bin/python main.py +# === With Managed Room (mainroom) === +cd mainroom/ctrl +./start.sh -d # Start detached +./start.sh amar # Start only amar +./start.sh soleprint # Start only soleprint +./stop.sh # Stop all +./deploy.sh # Deploy to AWS -# Health check -curl localhost:12000/health +# === Bare-metal Dev === +cd gen +.venv/bin/python run.py # Single-port dev server -# Build for deployment -python build.py deploy --output /path/to/deploy/ --cfg amar - -# Deploy via ctrl scripts -cd mainroom/soleprint/ctrl/local && ./deploy.sh - -# Docker (via mainroom) -cd mainroom/soleprint && docker compose up -d - -# Modelgen (generic tool) -python -m station.tools.modelgen from-config -c cfg/soleprint.config.json -o gen/models/ +# === Health Checks === +curl localhost:12000/health # Soleprint +curl localhost:8000/health # Amar backend ``` + +## Integration with ppl/ (Infrastructure) + +The `ppl/` repo manages infrastructure alongside spr: + +``` +wdir/ +├── spr/ # This repo (soleprint) +├── ppl/ # Pipelines & infrastructure +│ ├── ctrl/ +│ │ ├── deploy-gen.sh # Build spr + deploy via mainroom +│ │ └── dns.sh # Route53 DNS management +│ ├── ci/ # Woodpecker CI configs +│ ├── gateway/ # Nginx/Caddy configs +│ └── pipelines/ # CI/CD pipelines +└── ama/ # Amar source code + ├── amar_django_back/ + └── amar_frontend/ +``` + +### Deploy from ppl/ +```bash +cd ppl/ctrl +./deploy-gen.sh # Build spr + deploy to AWS +./deploy-gen.sh --dry-run # Preview +./dns.sh add soleprint # Add soleprint.mcrn.ar DNS +``` + +### Server Structure (mcrn.ar) +``` +~/mainroom/ # Deployed mainroom +├── amar/ # Amar Docker services +├── soleprint/ # Soleprint Docker services +└── ctrl/ # Server-side scripts + +# Services run on: +soleprint.mcrn.ar:12000 # Soleprint +amar.mcrn.ar # Amar (nginx proxied) +``` + +### Adding New Services +1. Add DNS: `ppl/ctrl/dns.sh add ` +2. Add nginx config in `ppl/gateway/` +3. Add docker-compose in `mainroom//` +4. Update `mainroom/ctrl/start.sh` if needed + +## External Paths + +| What | Path | +|------|------| +| Amar Backend | /home/mariano/wdir/ama/amar_django_back | +| Amar Frontend | /home/mariano/wdir/ama/amar_frontend | +| Venv | /home/mariano/wdir/venv/spr | +| Pipelines | /home/mariano/wdir/ppl | + +## Files Ignored + +- `gen/` - Regenerate with `python build.py dev` +- `fails/`, `def/` - Drafts +- `__pycache__/`, `.venv/` diff --git a/README.md b/README.md index 23e6db6..233b597 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,19 @@ > Cada paso deja huella / Each step leaves a mark -Development workflow and documentation platform. Run, test, and document everything in one place. +Development workflow platform. Run, test, and document everything in one place. ## Quick Start ```bash -# Build dev environment -python build.py dev +# Build +python build.py dev --cfg amar -# Run -cd gen -python3 -m venv .venv -.venv/bin/pip install -r requirements.txt -.venv/bin/python run.py # Single-port bare-metal dev +# Run standalone (Docker) +./ctrl/start.sh + +# Or bare-metal +cd gen && .venv/bin/python run.py # Visit http://localhost:12000 ``` @@ -23,73 +23,72 @@ python3 -m venv .venv | | System | What it does | |---|--------|--------------| | 👣 | **Soleprint** | Core coordinator, routing, landing page | -| 💉 | **Artery** | Connectors to external services (Jira, Slack, APIs) | +| 💉 | **Artery** | Connectors to external services (Jira, Slack, Google) | | 🗺️ | **Atlas** | Actionable documentation (BDD, Gherkin, specs) | -| 🎛️ | **Station** | Tools, environments, test runners | +| 🎛️ | **Station** | Tools (modelgen, tester) and monitors (databrowse) | ## Structure ``` spr/ -├── schema.json # Model definitions (source of truth) -├── cfg/ # Framework configuration +├── build.py # Build tool +├── cfg/ # Room configurations │ ├── soleprint.config.json -│ └── amar/ # Room-specific configs +│ └── amar/ # AMAR room (docker-compose, tests, models) +├── ctrl/ # Standalone Docker scripts │ -├── soleprint/ # Core coordinator (versioned) -│ ├── main.py # Multi-port entry point -│ ├── run.py # Single-port bare-metal dev -│ └── dataloader/ +├── artery/ # Connectors +│ ├── veins/ # Jira, Slack, Google +│ ├── shunts/ # Fake connectors for testing +│ └── plexuses/ # Full apps (backend + frontend) │ -├── artery/ # Connectors (versioned) -├── atlas/ # Documentation (versioned) -├── station/ # Tools (versioned) -│ └── tools/ -│ ├── modelgen/ # Generates models from config -│ ├── datagen/ # Test data generation -│ └── tester/ # BDD/contract test runner +├── atlas/ # Documentation +│ └── book/ # Gherkin samples, feature docs │ -├── data/ # JSON content -├── gen/ # Runnable instance (symlinks + generated) +├── station/ # Tools & monitors +│ ├── tools/ # modelgen, tester, datagen +│ └── monitors/ # databrowse │ -└── mainroom/ # Orchestration: soleprint ↔ managed room - ├── ctrl/ # Orchestration commands +├── soleprint/ # Core (versioned) +├── gen/ # Built instance (gitignored) +│ +└── mainroom/ # Orchestration with managed room + ├── amar -> cfg/amar + ├── soleprint/ # Soleprint Docker config ├── sbwrapper/ # Sidebar wrapper UI - └── soleprint/ # Docker configs + └── ctrl/ # start, stop, deploy scripts ``` -## Components +## Artery Hierarchy -**Shared:** -- **Room** - Environment configuration -- **Depot** - Data storage +``` +Vein ──► Pulse ──► Plexus +│ │ │ +│ │ └── Full app (backend + frontend + DB) +│ └── Composed (Vein + Room + Depot) +└── Stateless API connector -**System-specific:** -- **Vein** (Artery) - Single connector -- **Template** (Atlas) - Doc pattern -- **Tool** (Station) - Utility - -**Composed:** -- **Pulse** = Vein + Room + Depot -- **Book** = Template + Depot -- **Desk** = Cabinet + Room + Depots - -## Development - -### Build and run -```bash -python build.py dev # Soleprint only -python build.py dev --cfg amar # With amar room config - -cd gen -.venv/bin/python run.py # Bare-metal single-port -.venv/bin/python main.py # Multi-port (production-like) +Shunt ── Fake connector for testing ``` -### Run with Docker (via mainroom) +## Usage + +### Standalone (soleprint only) ```bash -cd mainroom/soleprint -docker compose up -d +python build.py dev +./ctrl/start.sh # Docker +./ctrl/stop.sh +``` + +### With Managed Room (amar + soleprint) +```bash +python build.py dev --cfg amar +docker network create soleprint_network + +cd mainroom/ctrl +./start.sh -d # Start detached +./stop.sh # Stop all +./deploy.sh # Deploy to AWS ``` ## Ports @@ -97,36 +96,25 @@ docker compose up -d | Service | Port | |---------|------| | Soleprint | 12000 | -| Artery | 12001 | -| Atlas | 12002 | -| Station | 12003 | +| Amar Backend | 8000 | +| Amar Frontend | 3000 | -## Architecture +## Tools -``` -soleprint/ → Versioned core files (main.py, run.py, dataloader) -gen/ → Runnable instance (symlinks to soleprint/ + systems) -gen/models/ → Generated once per client (like install) -cfg/ → Configuration (copied to gen/cfg/) +| Tool | Purpose | +|------|---------| +| modelgen | Generate models from config | +| tester | BDD/playwright test runner | +| datagen | Test data generation | +| databrowse | SQL data browser | -mainroom/ → Orchestration layer -├── sbwrapper → UI overlay for managed apps -└── soleprint → Docker for soleprint services -``` +## Veins -## Background - -Born from the friction of: -- Testing requiring PRs on small teams -- Documentation scattered across tools -- Quick API connectors taking too long to set up -- No self-contained environment to experiment freely - -Soleprint lets you run everything in isolation while building reusable pieces. - -## License - -TBD +| Vein | Auth | +|------|------| +| jira | Token | +| slack | Token | +| google | OAuth2 | --- diff --git a/artery/__init__.py b/artery/__init__.py index 4528c22..044f5da 100644 --- a/artery/__init__.py +++ b/artery/__init__.py @@ -16,10 +16,15 @@ Hierarchy (simple → complex): └── Stateless API connector (e.g., Jira client, Slack client) + + Shunt ─── Fake connector for testing + (e.g., mercadopago shunt with configurable responses) + Components: - veins/ Stateless connectors (core/ + api/) - pulses/ Composed: Vein + Room + Depot - plexuses/ Full applications with frontend + - shunts/ Fake connectors for testing (configurable responses) - rooms/ Environment configs - depots/ Data storage @@ -31,6 +36,12 @@ Differences: | Frontend | Optional test UI | None (uses vein) | Required full frontend | | Webhooks | No | No | Yes | | Deploy | With soleprint | With soleprint | Self-contained (docker) | + + | Aspect | Shunt | + |------------|---------------------------------------------------------------| + | Purpose | Fake/mock external service for testing | + | Frontend | Config UI to set responses | + | Deploy | With soleprint (replaces real vein during testing) | """ from . import veins diff --git a/artery/shunts/__init__.py b/artery/shunts/__init__.py new file mode 100644 index 0000000..3a2ccfd --- /dev/null +++ b/artery/shunts/__init__.py @@ -0,0 +1,18 @@ +""" +Shunts - Fake connectors for testing. + +A shunt redirects flow when the real service isn't available. +Each shunt mimics a vein's interface with configurable responses. + +Structure: + shunts// + ├── main.py # FastAPI with config UI + ├── depot/ + │ └── responses.json # Configurable fake responses + └── README.md + +Usage: + 1. Start the shunt instead of the real vein + 2. Configure responses via the UI or responses.json + 3. Run tests against the shunt +""" diff --git a/artery/shunts/example/README.md b/artery/shunts/example/README.md new file mode 100644 index 0000000..39a0654 --- /dev/null +++ b/artery/shunts/example/README.md @@ -0,0 +1,37 @@ +# Example Shunt + +Template for creating fake service connectors for testing. + +## Usage + +```bash +# Run the shunt +python main.py + +# Or with uvicorn +uvicorn main:app --port 8099 --reload +``` + +## Creating a New Shunt + +1. Copy this directory: + ```bash + cp -r shunts/example shunts/mercadopago + ``` + +2. Edit `depot/responses.json` with fake responses + +3. Update `main.py` to match the real vein's API endpoints + +## Configuration + +Edit `depot/responses.json` to configure fake responses: + +```json +{ + "GET /endpoint": {"response": "data"}, + "POST /endpoint": {"success": true} +} +``` + +The shunt UI at `/` shows current configuration. diff --git a/artery/shunts/example/depot/responses.json b/artery/shunts/example/depot/responses.json new file mode 100644 index 0000000..4f75fb8 --- /dev/null +++ b/artery/shunts/example/depot/responses.json @@ -0,0 +1,15 @@ +{ + "GET /users": [ + {"id": 1, "name": "Test User", "email": "test@example.com"} + ], + "GET /users/1": { + "id": 1, + "name": "Test User", + "email": "test@example.com" + }, + "POST /users": { + "id": 2, + "name": "Created User", + "success": true + } +} diff --git a/artery/shunts/example/main.py b/artery/shunts/example/main.py new file mode 100644 index 0000000..304bb8b --- /dev/null +++ b/artery/shunts/example/main.py @@ -0,0 +1,93 @@ +""" +Example Shunt - Template for creating fake service connectors. + +Copy this to create a new shunt: + cp -r shunts/example shunts/mercadopago + +Then customize: + - Update responses.json with fake responses + - Add endpoints matching the real vein's API +""" + +import json +import os +from pathlib import Path + +from fastapi import FastAPI, Request +from fastapi.responses import HTMLResponse, JSONResponse + +app = FastAPI(title="Example Shunt", description="Fake service for testing") + +BASE_DIR = Path(__file__).parent +DEPOT_DIR = Path(os.getenv("SHUNT_DEPOT", str(BASE_DIR / "depot"))) + +# Load responses +RESPONSES_FILE = DEPOT_DIR / "responses.json" +responses = {} +if RESPONSES_FILE.exists(): + responses = json.loads(RESPONSES_FILE.read_text()) + + +@app.get("/health") +def health(): + return {"status": "ok", "shunt": "example"} + + +@app.get("/", response_class=HTMLResponse) +def config_ui(): + """Simple UI to view/edit responses.""" + return f""" + + + + Example Shunt + + + +

Example Shunt

+

This shunt returns configurable fake responses for testing.

+

Current Responses

+
{json.dumps(responses, indent=2)}
+

Edit {RESPONSES_FILE} to change responses.

+ + + """ + + +@app.get("/api/{endpoint:path}") +def fake_get(endpoint: str): + """Return configured response for GET requests.""" + key = f"GET /{endpoint}" + if key in responses: + return responses[key] + return {"error": "not configured", "endpoint": endpoint, "method": "GET"} + + +@app.post("/api/{endpoint:path}") +async def fake_post(endpoint: str, request: Request): + """Return configured response for POST requests.""" + key = f"POST /{endpoint}" + if key in responses: + return responses[key] + body = ( + await request.json() + if request.headers.get("content-type") == "application/json" + else {} + ) + return { + "error": "not configured", + "endpoint": endpoint, + "method": "POST", + "received": body, + } + + +if __name__ == "__main__": + import uvicorn + + uvicorn.run(app, host="0.0.0.0", port=8099) diff --git a/atlas/.dockerignore b/atlas/.dockerignore new file mode 100644 index 0000000..50da7ae --- /dev/null +++ b/atlas/.dockerignore @@ -0,0 +1,56 @@ +# Python cache +__pycache__/ +*.py[cod] +*$py.class +*.so + +# Virtual environments +venv/ +env/ +.venv/ +ENV/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +.DS_Store + +# Git +.git/ +.gitignore +.gitattributes + +# Environment files +.env +.env.* +!.env.example + +# Testing +.pytest_cache/ +.coverage +htmlcov/ +.tox/ + +# Build artifacts +dist/ +build/ +*.egg-info/ + +# Data and logs +data/ +*.log +*.db +*.sqlite +*.sqlite3 + +# Documentation +*.md +!README.md + +# Node (for frontend) +node_modules/ +npm-debug.log +yarn-error.log +.next/ diff --git a/atlas/.gitignore b/atlas/.gitignore new file mode 100644 index 0000000..80b81ae --- /dev/null +++ b/atlas/.gitignore @@ -0,0 +1,3 @@ +def +__pycache__ +drive \ No newline at end of file diff --git a/atlas/CLAUDE.md b/atlas/CLAUDE.md new file mode 100644 index 0000000..33f9772 --- /dev/null +++ b/atlas/CLAUDE.md @@ -0,0 +1,116 @@ +# ALBUM: Documentation System + +## Purpose +Documentation, templates, and data. Composed into books. + +### Book Types + +**Single-larder book** (template: null) +- Book and larder are interchangeable +- Just static content served directly +- Listed in books.json, NOT in larders.json + +**Templated book** (template: {...}) +- Template defines the structure +- Larder must contain only elements matching that template +- Template validates/constrains the larder content +- Shows a styled landing page linking to template and larder + +### Larders vs Books + +- **Larders** = buckets that connect to other systems (e.g., drive, external data sources) +- **Books** = standalone content, may be single-larder or template+larder +- Single-larder books are listed as books, not larders + +### Templated Book Structure + +A templated book must contain a `template/` folder with the template definition inside: + +``` +book/{book-slug}/ +├── template/ # REQUIRED for templated books +│ └── {template-files} +├── {larder-name}/ # The actual data (marked with .larder file) +│ └── .larder +├── index.html # Larder browser (used at /book/{slug}/larder/) +└── detail.html # Detail view template (if needed) +``` + +Example: +``` +book/feature-form-samples/ +├── template/ # Template definition +│ └── plantilla-flujo.md +├── feature-form/ # Larder data (constrained by template) +│ ├── .larder +│ ├── pet-owner/ +│ ├── veterinarian/ +│ └── backoffice/ +├── index.html # Larder browser +└── detail.html # Detail renderer +``` + +Routes for templated books: +- `/book/{slug}/` → Landing page (template + larder links) +- `/book/{slug}/template/` → Template definition +- `/book/{slug}/larder/` → Larder browser + +## Components + +### Template (Patterns) + +**Gherkin/BDD** +- Status: Pending +- Goal: .feature files, simple templates for non-tech + +**Index Templates** +- Status: Pending +- Goal: HTML generators for indexes + +### Vault (Data) + +**drive/** +- Status: Downloaded +- Contents: Company drive (identity, marketing, ops, supply, finance, clients, pitches) + +### Book (Composed Docs) + +**drive-index** +- Status: Priority +- Goal: Two indexes (internal full, public privacy-conscious) + +**flow-docs** +- Status: Pending +- Goal: User flow documentation (pet owner, vet, ops) + +## Upward Report +``` +ALBUM: Drive index priority. Template + vault → book composition defined. +``` + +## Priority +1. Drive index book (HTML from vault/drive) +2. One gherkin template example +3. Flow documentation structure + +## Vault Contents (vault/drive) +- 01.Identidad Amar Mascotas +- 02. Marketing contenidos +- 03. Marketing Growth +- 05. ATC - Operaciones +- 06. Supply (vetes-labo-clinicas) +- 07. Finanzas y contabilidad +- Clientes - ventas - devoluciones +- Pitch Decks - Presentaciones + +## Deployment + +- **URL**: https://album.mcrn.ar +- **Port**: 12002 +- **Service**: `systemctl status album` + +FastAPI app serving documentation. Currently serves index.html at root, expandable for book browsing. + +## Upstream (for main pawprint thread) + +This is now a separate repo. See pawprint/UPSTREAM.md for merge notes. diff --git a/atlas/book-template.html b/atlas/book-template.html new file mode 100644 index 0000000..79136ac --- /dev/null +++ b/atlas/book-template.html @@ -0,0 +1,118 @@ + + + + + + {{ book.title }} · Album + + + +
+ +

{{ book.title }}

+
+

Templated book

+ +
+ +
+ + + + diff --git a/atlas/book/arch-model/01-backend-architecture.dot b/atlas/book/arch-model/01-backend-architecture.dot new file mode 100644 index 0000000..7c6da1c --- /dev/null +++ b/atlas/book/arch-model/01-backend-architecture.dot @@ -0,0 +1,184 @@ +digraph BackendArchitecture { + // Graph settings + rankdir=TB + compound=true + splines=ortho + node [shape=box, style="rounded,filled", fontname="Helvetica", fontsize=11] + edge [fontname="Helvetica", fontsize=9] + + // Color scheme + // Django Core: #092E20 (dark green) + // Mascotas: #4A90D9 (blue) + // Productos: #50C878 (green) + // Solicitudes: #FF6B6B (coral) + // Common: #9B59B6 (purple) + // External: #F39C12 (orange) + // Payments: #E74C3C (red) + + label="AMAR Mascotas - Backend Architecture (Django)\n\n" + labelloc="t" + fontsize=16 + fontname="Helvetica-Bold" + + // Django Core Cluster + subgraph cluster_django_core { + label="Django Core" + style="rounded,filled" + fillcolor="#E8F5E9" + color="#2E7D32" + + auth_user [label="auth.User\n(Django Auth)", fillcolor="#C8E6C9"] + django_admin [label="Django Admin\nInterface", fillcolor="#C8E6C9"] + drf [label="Django REST\nFramework", fillcolor="#C8E6C9"] + jwt_auth [label="JWT Authentication\n(SimpleJWT)", fillcolor="#C8E6C9"] + } + + // Mascotas App Cluster + subgraph cluster_mascotas { + label="mascotas (Pets & Veterinarians)" + style="rounded,filled" + fillcolor="#E3F2FD" + color="#1565C0" + + petowner [label="PetOwner\n(Cliente/Tutor)", fillcolor="#BBDEFB"] + pet [label="Pet\n(Mascota)", fillcolor="#BBDEFB"] + veterinarian [label="Veterinarian\n(Profesional)", fillcolor="#BBDEFB"] + vetvisit [label="VetVisit\n(Consulta)", fillcolor="#BBDEFB"] + vetvisitreport [label="VetVisitReport\n(Informe Clinico)", fillcolor="#BBDEFB"] + availability [label="Availability /\nUnavailability", fillcolor="#BBDEFB"] + pet_health [label="PetVaccine /\nPetStudy", fillcolor="#BBDEFB"] + } + + // Productos App Cluster + subgraph cluster_productos { + label="productos (Services & Pricing)" + style="rounded,filled" + fillcolor="#E8F5E9" + color="#2E7D32" + + service [label="Service\n(Servicio)", fillcolor="#C8E6C9"] + category [label="Category / Group\n(Categorias)", fillcolor="#C8E6C9"] + prices [label="Prices\n(Precios)", fillcolor="#C8E6C9"] + discounts [label="Discounts\n(Descuentos)", fillcolor="#C8E6C9"] + cart [label="Cart / CartItem\n(Carrito)", fillcolor="#C8E6C9"] + combo [label="ServiceCombo\n(Paquetes)", fillcolor="#C8E6C9"] + } + + // Solicitudes App Cluster + subgraph cluster_solicitudes { + label="solicitudes (Service Requests)" + style="rounded,filled" + fillcolor="#FFEBEE" + color="#C62828" + + servicerequest [label="ServiceRequest\n(Solicitud)", fillcolor="#FFCDD2"] + statehistory [label="StateHistory\n(Historial)", fillcolor="#FFCDD2"] + vetasked [label="VeterinarianAsked\n(Vet Consultado)", fillcolor="#FFCDD2"] + reminders [label="Reminders\n(Recordatorios)", fillcolor="#FFCDD2"] + } + + // Common App Cluster + subgraph cluster_common { + label="common (Shared Models)" + style="rounded,filled" + fillcolor="#F3E5F5" + color="#7B1FA2" + + campaign [label="Campaign\n(Marketing)", fillcolor="#E1BEE7"] + tag [label="Tag\n(Etiquetas)", fillcolor="#E1BEE7"] + specialty [label="Specialty\n(Especialidades)", fillcolor="#E1BEE7"] + medication [label="Medication\n(Medicamentos)", fillcolor="#E1BEE7"] + breed [label="PetBreed / Vaccine\n/ Study", fillcolor="#E1BEE7"] + neighborhood [label="Neighborhood /\nProvince / Locality", fillcolor="#E1BEE7"] + turnfee [label="IndividualTurnFee\nGroup", fillcolor="#E1BEE7"] + } + + // Payments App Cluster + subgraph cluster_payments { + label="payments (Payment Processing)" + style="rounded,filled" + fillcolor="#FCE4EC" + color="#AD1457" + + mercadopago [label="MercadoPago\nAccount", fillcolor="#F8BBD9"] + mpnotification [label="MP Notification\n(Webhooks)", fillcolor="#F8BBD9"] + } + + // External Integrations Cluster + subgraph cluster_external { + label="External Integrations" + style="rounded,filled" + fillcolor="#FFF3E0" + color="#E65100" + + google_cal [label="Google Calendar\n(Agenda)", fillcolor="#FFE0B2"] + google_sheets [label="Google Sheets\n(Exports)", fillcolor="#FFE0B2"] + mercately [label="Mercately\n(WhatsApp)", fillcolor="#FFE0B2"] + afip [label="AFIP\n(Facturacion)", fillcolor="#FFE0B2"] + celery [label="Celery\n(Async Tasks)", fillcolor="#FFE0B2"] + } + + // AFIP Integration + subgraph cluster_afip { + label="django_afip (Invoicing)" + style="rounded,filled" + fillcolor="#FFFDE7" + color="#F9A825" + + receipt [label="Receipt\n(Comprobante)", fillcolor="#FFF9C4"] + taxpayer [label="TaxPayer\n(Contribuyente)", fillcolor="#FFF9C4"] + pos [label="PointOfSales\n(Punto de Venta)", fillcolor="#FFF9C4"] + } + + // Relationships - Core + auth_user -> petowner [label="1:1 optional", color="#666"] + auth_user -> veterinarian [label="1:1", color="#666"] + drf -> jwt_auth [style=dashed, color="#999"] + + // Relationships - Mascotas + petowner -> pet [label="1:N", color="#1565C0"] + petowner -> servicerequest [label="1:N", color="#1565C0"] + pet -> vetvisit [label="N:M", color="#1565C0"] + pet -> pet_health [label="1:N", color="#1565C0"] + veterinarian -> vetvisit [label="1:N", color="#1565C0"] + veterinarian -> availability [label="1:N", color="#1565C0"] + vetvisit -> vetvisitreport [label="1:N", color="#1565C0"] + vetvisit -> servicerequest [label="1:1", style=dashed, color="#1565C0"] + + // Relationships - Productos + service -> category [label="N:1", color="#2E7D32"] + service -> prices [label="1:N", color="#2E7D32"] + service -> discounts [label="1:N", color="#2E7D32"] + cart -> petowner [label="N:1", color="#2E7D32"] + cart -> veterinarian [label="N:1 optional", color="#2E7D32", style=dashed] + service -> cart [label="via CartItem", color="#2E7D32"] + combo -> service [label="contains", color="#2E7D32"] + + // Relationships - Solicitudes + servicerequest -> cart [label="1:1", color="#C62828"] + servicerequest -> statehistory [label="1:N", color="#C62828"] + servicerequest -> vetasked [label="1:N", color="#C62828"] + vetasked -> reminders [label="1:N", color="#C62828"] + veterinarian -> vetasked [label="N:1", color="#C62828"] + + // Relationships - Common + petowner -> neighborhood [label="N:1", color="#7B1FA2"] + veterinarian -> specialty [label="N:M", color="#7B1FA2"] + veterinarian -> neighborhood [label="N:M coverage", color="#7B1FA2"] + servicerequest -> campaign [label="N:1 optional", color="#7B1FA2", style=dashed] + servicerequest -> tag [label="N:M", color="#7B1FA2"] + vetvisitreport -> medication [label="references", color="#7B1FA2", style=dashed] + veterinarian -> turnfee [label="N:M", color="#7B1FA2"] + + // Relationships - Payments & External + servicerequest -> mercadopago [label="payment", color="#AD1457"] + mpnotification -> servicerequest [label="confirms", color="#AD1457"] + vetvisit -> google_cal [label="sync", color="#E65100", style=dashed] + servicerequest -> mercately [label="notify", color="#E65100", style=dashed] + reminders -> celery [label="scheduled", color="#E65100", style=dashed] + + // AFIP relationships + vetvisit -> receipt [label="1:1 optional", color="#F9A825", style=dashed] + receipt -> taxpayer [label="N:1", color="#F9A825"] + receipt -> pos [label="N:1", color="#F9A825"] +} diff --git a/atlas/book/arch-model/01-backend-architecture.svg b/atlas/book/arch-model/01-backend-architecture.svg new file mode 100644 index 0000000..e8754ae --- /dev/null +++ b/atlas/book/arch-model/01-backend-architecture.svg @@ -0,0 +1,585 @@ + + + + + + +BackendArchitecture + +AMAR Mascotas - Backend Architecture (Django) + +cluster_django_core + +Django Core + + +cluster_mascotas + +mascotas (Pets & Veterinarians) + + +cluster_productos + +productos (Services & Pricing) + + +cluster_solicitudes + +solicitudes (Service Requests) + + +cluster_common + +common (Shared Models) + + +cluster_payments + +payments (Payment Processing) + + +cluster_external + +External Integrations + + +cluster_afip + +django_afip (Invoicing) + + + +auth_user + +auth.User +(Django Auth) + + + +petowner + +PetOwner +(Cliente/Tutor) + + + +auth_user->petowner + + +1:1 optional + + + +veterinarian + +Veterinarian +(Profesional) + + + +auth_user->veterinarian + + +1:1 + + + +django_admin + +Django Admin +Interface + + + +drf + +Django REST +Framework + + + +jwt_auth + +JWT Authentication +(SimpleJWT) + + + +drf->jwt_auth + + + + + +pet + +Pet +(Mascota) + + + +petowner->pet + + +1:N + + + +servicerequest + +ServiceRequest +(Solicitud) + + + +petowner->servicerequest + + +1:N + + + +neighborhood + +Neighborhood / +Province / Locality + + + +petowner->neighborhood + + +N:1 + + + +vetvisit + +VetVisit +(Consulta) + + + +pet->vetvisit + + +N:M + + + +pet_health + +PetVaccine / +PetStudy + + + +pet->pet_health + + +1:N + + + +veterinarian->vetvisit + + +1:N + + + +availability + +Availability / +Unavailability + + + +veterinarian->availability + + +1:N + + + +vetasked + +VeterinarianAsked +(Vet Consultado) + + + +veterinarian->vetasked + + +N:1 + + + +specialty + +Specialty +(Especialidades) + + + +veterinarian->specialty + + +N:M + + + +veterinarian->neighborhood + + +N:M coverage + + + +turnfee + +IndividualTurnFee +Group + + + +veterinarian->turnfee + + +N:M + + + +vetvisitreport + +VetVisitReport +(Informe Clinico) + + + +vetvisit->vetvisitreport + + +1:N + + + +vetvisit->servicerequest + + +1:1 + + + +google_cal + +Google Calendar +(Agenda) + + + +vetvisit->google_cal + + +sync + + + +receipt + +Receipt +(Comprobante) + + + +vetvisit->receipt + + +1:1 optional + + + +medication + +Medication +(Medicamentos) + + + +vetvisitreport->medication + + +references + + + +service + +Service +(Servicio) + + + +category + +Category / Group +(Categorias) + + + +service->category + + +N:1 + + + +prices + +Prices +(Precios) + + + +service->prices + + +1:N + + + +discounts + +Discounts +(Descuentos) + + + +service->discounts + + +1:N + + + +cart + +Cart / CartItem +(Carrito) + + + +service->cart + + +via CartItem + + + +cart->petowner + + +N:1 + + + +cart->veterinarian + + +N:1 optional + + + +combo + +ServiceCombo +(Paquetes) + + + +combo->service + + +contains + + + +servicerequest->cart + + +1:1 + + + +statehistory + +StateHistory +(Historial) + + + +servicerequest->statehistory + + +1:N + + + +servicerequest->vetasked + + +1:N + + + +campaign + +Campaign +(Marketing) + + + +servicerequest->campaign + + +N:1 optional + + + +tag + +Tag +(Etiquetas) + + + +servicerequest->tag + + +N:M + + + +mercadopago + +MercadoPago +Account + + + +servicerequest->mercadopago + + +payment + + + +mercately + +Mercately +(WhatsApp) + + + +servicerequest->mercately + + +notify + + + +reminders + +Reminders +(Recordatorios) + + + +vetasked->reminders + + +1:N + + + +celery + +Celery +(Async Tasks) + + + +reminders->celery + + +scheduled + + + +breed + +PetBreed / Vaccine +/ Study + + + +mpnotification + +MP Notification +(Webhooks) + + + +mpnotification->servicerequest + + +confirms + + + +google_sheets + +Google Sheets +(Exports) + + + +afip + +AFIP +(Facturacion) + + + +taxpayer + +TaxPayer +(Contribuyente) + + + +receipt->taxpayer + + +N:1 + + + +pos + +PointOfSales +(Punto de Venta) + + + +receipt->pos + + +N:1 + + + diff --git a/atlas/book/arch-model/02-frontend-architecture.dot b/atlas/book/arch-model/02-frontend-architecture.dot new file mode 100644 index 0000000..ff349aa --- /dev/null +++ b/atlas/book/arch-model/02-frontend-architecture.dot @@ -0,0 +1,184 @@ +digraph FrontendArchitecture { + // Graph settings + rankdir=TB + compound=true + splines=ortho + node [shape=box, style="rounded,filled", fontname="Helvetica", fontsize=11] + edge [fontname="Helvetica", fontsize=9] + + label="AMAR Mascotas - Frontend Architecture (Next.js)\n\n" + labelloc="t" + fontsize=16 + fontname="Helvetica-Bold" + + // Next.js Core Cluster + subgraph cluster_nextjs { + label="Next.js 13+ (App Router)" + style="rounded,filled" + fillcolor="#E3F2FD" + color="#1565C0" + + app_router [label="App Router\n/app/*", fillcolor="#BBDEFB"] + layout [label="Layout Components\n(RootLayout, etc)", fillcolor="#BBDEFB"] + middleware [label="Middleware\n(Auth redirect)", fillcolor="#BBDEFB"] + } + + // Public Frontend Cluster + subgraph cluster_public { + label="Public Frontend (/(frontend))" + style="rounded,filled" + fillcolor="#E8F5E9" + color="#2E7D32" + + home [label="Home Page\n/", fillcolor="#C8E6C9"] + services_page [label="Services Catalog\n/servicios", fillcolor="#C8E6C9"] + cart_page [label="Cart\n/carrito", fillcolor="#C8E6C9"] + login_page [label="Login/Register\n/login", fillcolor="#C8E6C9"] + profile_page [label="User Profile\n/perfil", fillcolor="#C8E6C9"] + pets_page [label="My Pets\n/mascotas", fillcolor="#C8E6C9"] + requests_page [label="My Requests\n/solicitudes", fillcolor="#C8E6C9"] + } + + // Backoffice Cluster + subgraph cluster_backoffice { + label="Backoffice (/(backoffice)/admin)" + style="rounded,filled" + fillcolor="#FFF3E0" + color="#E65100" + + admin_dash [label="Dashboard\n/admin", fillcolor="#FFE0B2"] + admin_visits [label="Visits Management\n/admin/visits", fillcolor="#FFE0B2"] + admin_pets [label="Pets Overview\n/admin/pets", fillcolor="#FFE0B2"] + admin_requests [label="Service Requests\n/admin/solicitudes", fillcolor="#FFE0B2"] + admin_calendar [label="Calendar View\n/admin/calendario", fillcolor="#FFE0B2"] + } + + // Components Cluster + subgraph cluster_components { + label="Shared Components (/components)" + style="rounded,filled" + fillcolor="#F3E5F5" + color="#7B1FA2" + + sidebar [label="Sidebar\nNavigation", fillcolor="#E1BEE7"] + navbar [label="NavbarBackoffice\nTop Bar", fillcolor="#E1BEE7"] + visits_section [label="VisitsSection\n(List + Actions)", fillcolor="#E1BEE7"] + drawer [label="VisitsDrawer\n(Side Panel)", fillcolor="#E1BEE7"] + tables [label="DataTable\nComponents", fillcolor="#E1BEE7"] + forms [label="Form Components\n(Pet, Visit, etc)", fillcolor="#E1BEE7"] + } + + // Services Layer Cluster + subgraph cluster_services { + label="Services Layer (/services)" + style="rounded,filled" + fillcolor="#FFEBEE" + color="#C62828" + + http_service [label="HttpService\n(Axios wrapper)", fillcolor="#FFCDD2"] + auth_api [label="authAPI\n(login/register)", fillcolor="#FFCDD2"] + visits_api [label="visitsAPI\n(CRUD visits)", fillcolor="#FFCDD2"] + orders_api [label="OrdersAPI\n(service requests)", fillcolor="#FFCDD2"] + petowners_api [label="petOwnersAPI\n(clients)", fillcolor="#FFCDD2"] + vets_api [label="VeterinariansAPI\n(professionals)", fillcolor="#FFCDD2"] + services_api [label="servicesAPI\n(catalog)", fillcolor="#FFCDD2"] + cart_api [label="CartAPI\n(shopping)", fillcolor="#FFCDD2"] + } + + // State Management Cluster + subgraph cluster_state { + label="State Management (/redux, /contexts)" + style="rounded,filled" + fillcolor="#E0F7FA" + color="#00838F" + + redux_store [label="Redux Store\n(Global State)", fillcolor="#B2EBF2"] + auth_slice [label="Auth Slice\n(user, token)", fillcolor="#B2EBF2"] + visits_slice [label="Visits Slice\n(visit data)", fillcolor="#B2EBF2"] + cart_slice [label="Cart Slice\n(items)", fillcolor="#B2EBF2"] + auth_context [label="AuthContext\n(Provider)", fillcolor="#B2EBF2"] + } + + // Backend API Cluster (external) + subgraph cluster_backend { + label="Django Backend API" + style="rounded,filled" + fillcolor="#ECEFF1" + color="#455A64" + + api_mascotas [label="/mascotas/api/v1/\n(Pets, Vets, Visits)", fillcolor="#CFD8DC"] + api_productos [label="/productos/\n(Services, Prices)", fillcolor="#CFD8DC"] + api_solicitudes [label="/solicitudes/\n(Requests)", fillcolor="#CFD8DC"] + api_auth [label="/api/token/\n(JWT Auth)", fillcolor="#CFD8DC"] + api_payments [label="/payments/\n(MercadoPago)", fillcolor="#CFD8DC"] + } + + // User Types + subgraph cluster_users { + label="User Types" + style="rounded,filled" + fillcolor="#FCE4EC" + color="#AD1457" + + petowner_user [label="PetOwner\n(Cliente)", shape=ellipse, fillcolor="#F8BBD9"] + vet_user [label="Veterinarian\n(Profesional)", shape=ellipse, fillcolor="#F8BBD9"] + admin_user [label="Admin/Staff\n(Interno)", shape=ellipse, fillcolor="#F8BBD9"] + } + + // Relationships - Router + app_router -> layout [color="#1565C0"] + app_router -> middleware [color="#1565C0"] + + // Public pages + home -> services_page [color="#2E7D32"] + services_page -> cart_page [color="#2E7D32"] + cart_page -> login_page [label="if not auth", color="#2E7D32", style=dashed] + login_page -> profile_page [color="#2E7D32"] + profile_page -> pets_page [color="#2E7D32"] + profile_page -> requests_page [color="#2E7D32"] + + // Backoffice pages + admin_dash -> admin_visits [color="#E65100"] + admin_dash -> admin_pets [color="#E65100"] + admin_dash -> admin_requests [color="#E65100"] + admin_dash -> admin_calendar [color="#E65100"] + + // Components used by pages + admin_visits -> visits_section [color="#7B1FA2", style=dashed] + admin_visits -> drawer [color="#7B1FA2", style=dashed] + admin_dash -> sidebar [color="#7B1FA2", style=dashed] + admin_dash -> navbar [color="#7B1FA2", style=dashed] + + // Services to APIs + http_service -> auth_api [color="#C62828"] + http_service -> visits_api [color="#C62828"] + http_service -> orders_api [color="#C62828"] + http_service -> petowners_api [color="#C62828"] + http_service -> vets_api [color="#C62828"] + http_service -> services_api [color="#C62828"] + http_service -> cart_api [color="#C62828"] + + // Services to Backend + auth_api -> api_auth [label="POST /api/token/", color="#455A64"] + visits_api -> api_mascotas [label="CRUD /vet-visits/", color="#455A64"] + orders_api -> api_solicitudes [label="CRUD /service-requests/", color="#455A64"] + petowners_api -> api_mascotas [label="CRUD /pet-owners/", color="#455A64"] + vets_api -> api_mascotas [label="GET /veterinarians/", color="#455A64"] + services_api -> api_productos [label="GET /services/", color="#455A64"] + cart_api -> api_productos [label="CRUD /cart/", color="#455A64"] + + // State connections + auth_slice -> redux_store [color="#00838F"] + visits_slice -> redux_store [color="#00838F"] + cart_slice -> redux_store [color="#00838F"] + auth_context -> redux_store [color="#00838F", style=dashed] + + // User access paths + petowner_user -> home [label="public access", color="#AD1457"] + petowner_user -> profile_page [label="authenticated", color="#AD1457", style=dashed] + vet_user -> admin_dash [label="backoffice", color="#AD1457"] + admin_user -> admin_dash [label="full access", color="#AD1457"] + + // API routing through HttpService + http_service -> api_auth [label="JWT refresh", color="#455A64", style=dashed] +} diff --git a/atlas/book/arch-model/02-frontend-architecture.svg b/atlas/book/arch-model/02-frontend-architecture.svg new file mode 100644 index 0000000..dc235d7 --- /dev/null +++ b/atlas/book/arch-model/02-frontend-architecture.svg @@ -0,0 +1,595 @@ + + + + + + +FrontendArchitecture + +AMAR Mascotas - Frontend Architecture (Next.js) + +cluster_nextjs + +Next.js 13+ (App Router) + + +cluster_public + +Public Frontend (/(frontend)) + + +cluster_backoffice + +Backoffice (/(backoffice)/admin) + + +cluster_components + +Shared Components (/components) + + +cluster_services + +Services Layer (/services) + + +cluster_state + +State Management (/redux, /contexts) + + +cluster_backend + +Django Backend API + + +cluster_users + +User Types + + + +app_router + +App Router +/app/* + + + +layout + +Layout Components +(RootLayout, etc) + + + +app_router->layout + + + + + +middleware + +Middleware +(Auth redirect) + + + +app_router->middleware + + + + + +home + +Home Page +/ + + + +services_page + +Services Catalog +/servicios + + + +home->services_page + + + + + +cart_page + +Cart +/carrito + + + +services_page->cart_page + + + + + +login_page + +Login/Register +/login + + + +cart_page->login_page + + +if not auth + + + +profile_page + +User Profile +/perfil + + + +login_page->profile_page + + + + + +pets_page + +My Pets +/mascotas + + + +profile_page->pets_page + + + + + +requests_page + +My Requests +/solicitudes + + + +profile_page->requests_page + + + + + +admin_dash + +Dashboard +/admin + + + +admin_visits + +Visits Management +/admin/visits + + + +admin_dash->admin_visits + + + + + +admin_pets + +Pets Overview +/admin/pets + + + +admin_dash->admin_pets + + + + + +admin_requests + +Service Requests +/admin/solicitudes + + + +admin_dash->admin_requests + + + + + +admin_calendar + +Calendar View +/admin/calendario + + + +admin_dash->admin_calendar + + + + + +sidebar + +Sidebar +Navigation + + + +admin_dash->sidebar + + + + + +navbar + +NavbarBackoffice +Top Bar + + + +admin_dash->navbar + + + + + +visits_section + +VisitsSection +(List + Actions) + + + +admin_visits->visits_section + + + + + +drawer + +VisitsDrawer +(Side Panel) + + + +admin_visits->drawer + + + + + +tables + +DataTable +Components + + + +forms + +Form Components +(Pet, Visit, etc) + + + +http_service + +HttpService +(Axios wrapper) + + + +auth_api + +authAPI +(login/register) + + + +http_service->auth_api + + + + + +visits_api + +visitsAPI +(CRUD visits) + + + +http_service->visits_api + + + + + +orders_api + +OrdersAPI +(service requests) + + + +http_service->orders_api + + + + + +petowners_api + +petOwnersAPI +(clients) + + + +http_service->petowners_api + + + + + +vets_api + +VeterinariansAPI +(professionals) + + + +http_service->vets_api + + + + + +services_api + +servicesAPI +(catalog) + + + +http_service->services_api + + + + + +cart_api + +CartAPI +(shopping) + + + +http_service->cart_api + + + + + +api_auth + +/api/token/ +(JWT Auth) + + + +http_service->api_auth + + +JWT refresh + + + +auth_api->api_auth + + +POST /api/token/ + + + +api_mascotas + +/mascotas/api/v1/ +(Pets, Vets, Visits) + + + +visits_api->api_mascotas + + +CRUD /vet-visits/ + + + +api_solicitudes + +/solicitudes/ +(Requests) + + + +orders_api->api_solicitudes + + +CRUD /service-requests/ + + + +petowners_api->api_mascotas + + +CRUD /pet-owners/ + + + +vets_api->api_mascotas + + +GET /veterinarians/ + + + +api_productos + +/productos/ +(Services, Prices) + + + +services_api->api_productos + + +GET /services/ + + + +cart_api->api_productos + + +CRUD /cart/ + + + +redux_store + +Redux Store +(Global State) + + + +auth_slice + +Auth Slice +(user, token) + + + +auth_slice->redux_store + + + + + +visits_slice + +Visits Slice +(visit data) + + + +visits_slice->redux_store + + + + + +cart_slice + +Cart Slice +(items) + + + +cart_slice->redux_store + + + + + +auth_context + +AuthContext +(Provider) + + + +auth_context->redux_store + + + + + +api_payments + +/payments/ +(MercadoPago) + + + +petowner_user + +PetOwner +(Cliente) + + + +petowner_user->home + + +public access + + + +petowner_user->profile_page + + +authenticated + + + +vet_user + +Veterinarian +(Profesional) + + + +vet_user->admin_dash + + +backoffice + + + +admin_user + +Admin/Staff +(Interno) + + + +admin_user->admin_dash + + +full access + + + diff --git a/atlas/book/arch-model/03-data-model.dot b/atlas/book/arch-model/03-data-model.dot new file mode 100644 index 0000000..bb2a092 --- /dev/null +++ b/atlas/book/arch-model/03-data-model.dot @@ -0,0 +1,234 @@ +digraph DataModel { + // Graph settings + rankdir=TB + compound=true + splines=ortho + node [shape=record, style="filled", fontname="Helvetica", fontsize=10] + edge [fontname="Helvetica", fontsize=8] + + label="AMAR Mascotas - Data Model (Entity Relationships)\n\n" + labelloc="t" + fontsize=16 + fontname="Helvetica-Bold" + + // === USERS & AUTHENTICATION === + subgraph cluster_auth { + label="Users & Authentication" + style="rounded,filled" + fillcolor="#E8F5E9" + color="#2E7D32" + + auth_user [label="{auth.User|id: PK\luser name: str\lemail: str\lis_staff: bool\lis_superuser: bool\l}", fillcolor="#C8E6C9"] + } + + // === PET OWNERS & PETS === + subgraph cluster_mascotas_owners { + label="Pet Owners & Pets" + style="rounded,filled" + fillcolor="#E3F2FD" + color="#1565C0" + + petowner [label="{PetOwner|id: PK\lemail: str (unique)\lphone: str\lfirst_name: str\llast_name: str\lneighborhood_id: FK\lcampaign_id: FK (opt)\lgeo_latitude: float\lgeo_longitude: float\laddress: str\l}", fillcolor="#BBDEFB"] + + pet [label="{Pet|id: PK\lowner_id: FK\lname: str\lpet_type: DOG/CAT\lbreed_id: FK (opt)\lgender: M/F\lage: int\lweight: decimal\lheight: decimal\lbirth_date: date\lallergies: text\lneutered: bool\lis_deceased: bool\lstate: puppy/adult/...\lprofile_picture: file\l}", fillcolor="#BBDEFB"] + + petvaccine [label="{PetVaccine|id: PK\lpet_id: FK\lvaccine_id: FK\lapplication_date: date\lnext_application: date\l}", fillcolor="#90CAF9"] + + petstudy [label="{PetStudy|id: PK\lpet_id: FK\lstudy_id: FK\ldate: date\lresult: text\limages: files\l}", fillcolor="#90CAF9"] + } + + // === VETERINARIANS === + subgraph cluster_veterinarians { + label="Veterinarians" + style="rounded,filled" + fillcolor="#FFF3E0" + color="#E65100" + + veterinarian [label="{Veterinarian|id: PK\luser_id: FK\llicense: str\lemail: str\lphone: str\l}", fillcolor="#FFE0B2"] + + availability [label="{Availability|id: PK\lveterinarian_id: FK\lstart_day: 0-6\lend_day: 0-6\lstart_time: time\lend_time: time\l}", fillcolor="#FFCC80"] + + unavailability [label="{Unavailability|id: PK\lveterinarian_id: FK\lstart_date: date\lend_date: date\lstart_time: time\lend_time: time\lreason: str\l}", fillcolor="#FFCC80"] + + vet_specialty [label="{M2M: Vet-Specialty|veterinarian_id: FK\lspecialty_id: FK\l}", shape=diamond, fillcolor="#FFB74D"] + + vet_neighborhood [label="{M2M: Vet-Neighborhood|veterinarian_id: FK\lneighborhood_id: FK\l}", shape=diamond, fillcolor="#FFB74D"] + } + + // === SERVICES & PRICING === + subgraph cluster_productos { + label="Services & Pricing" + style="rounded,filled" + fillcolor="#F3E5F5" + color="#7B1FA2" + + grupo [label="{Group|id: PK\lname: str\ldescription: text\l}", fillcolor="#E1BEE7"] + + category [label="{Category|id: PK\lgroup_id: FK\lname: str\ldescription: text\lvalue: int (order)\l}", fillcolor="#E1BEE7"] + + service [label="{Service|id: PK\lname: str\ldescription: text\lspecialty_id: FK\lcategory_id: FK\lduration: int (min)\lmodality: onsite/online\lpayment_sign_req: bool\lpet_type_filter: str\lage_filter: str\lweight_range: str\l}", fillcolor="#CE93D8"] + + prices [label="{Prices|id: PK\lservice_id: FK\lveterinarian_id: FK (opt)\lprice: decimal\lprofessional_fee: decimal\lpayment_sign: decimal\lfrom_date: date\lto_date: date\lactive: bool\l}", fillcolor="#BA68C8"] + + discounts [label="{Discounts|id: PK\lservice_id: FK\ldiscount: decimal (%)\lfrom_date: date\lto_date: date\lactive: bool\l}", fillcolor="#BA68C8"] + + servicecombo [label="{ServiceCombo|id: PK\lname: str\ldescription: text\ldiscount_percent: decimal\ldiscount_fixed: decimal\l}", fillcolor="#E1BEE7"] + } + + // === CART & CHECKOUT === + subgraph cluster_cart { + label="Cart & Checkout" + style="rounded,filled" + fillcolor="#E0F7FA" + color="#00838F" + + cart [label="{Cart|id: PK\lpetowner_id: FK\lveterinarian_id: FK (opt)\luse_vet_prices: bool\lapply_turn_fee: bool\l}", fillcolor="#B2EBF2"] + + cartitem [label="{CartItem|id: PK\lcart_id: FK\lpet_id: FK (opt)\lservice_id: FK\lprice: decimal\lquantity: int\ltotal: decimal (calc)\l}", fillcolor="#80DEEA"] + + cartresumeitem [label="{CartResumeItem|id: PK\lcart_id: FK\lconcept: SUBTOTAL/\l DESCUENTO/\l ADELANTO/\l TOTAL/\l COSTO_SERVICIO\lamount: decimal\lorder: int\l}", fillcolor="#80DEEA"] + + cartpetreason [label="{CartPetReason|id: PK\lcart_id: FK\lpet_id: FK\lreason: text\l}", fillcolor="#80DEEA"] + } + + // === SERVICE REQUESTS === + subgraph cluster_solicitudes { + label="Service Requests (Workflow)" + style="rounded,filled" + fillcolor="#FFEBEE" + color="#C62828" + + servicerequest [label="{ServiceRequest|id: PK\lpetowner_id: FK\lcart_id: FK\lveterinarian_id: FK (opt)\lstate: pending/vet_asked/\l vet_accepted/coordinated/\l payed/Confirmado/...\lreason: text\ldays_requested: JSON\ldate_coordinated: datetime\lhour_coordinated: time\lpay_number: str\lcampaign_id: FK (opt)\lattended_by_id: FK (opt)\l}", fillcolor="#FFCDD2"] + + statehistory [label="{StateHistory|id: PK\lservice_request_id: FK\lstate: str\ladditional_data: JSON\lcreated_at: datetime\luser_id: FK\l}", fillcolor="#EF9A9A"] + + vetasked [label="{VeterinarianAsked|id: PK\lservice_request_id: FK\lveterinarian_id: FK\ldate_asked: datetime\ldate_answered: datetime\laccepted: bool\l}", fillcolor="#EF9A9A"] + + vetreminder [label="{ScheduledVetReminder|id: PK\lvet_asked_id: FK\lscheduled_for: datetime\lprocessed_at: datetime\lstatus: pending/sent/...\lcelery_task_id: str\l}", fillcolor="#E57373"] + + payreminder [label="{PaymentReminder|id: PK\lservice_request_id: FK\lscheduled_for: datetime\lprocessed_at: datetime\lstatus: pending/sent/...\l}", fillcolor="#E57373"] + } + + // === VET VISITS === + subgraph cluster_vetvisits { + label="Veterinary Visits" + style="rounded,filled" + fillcolor="#FFFDE7" + color="#F9A825" + + vetvisit [label="{VetVisit|id: PK\lservice_request_id: FK (opt)\lowner_id: FK\lveterinarian_id: FK\ldate: date\lhour: time\lvisit_type: clinical/\l vaccination/\l telemedicina\lvisit_state: PENDING/\l IN_PROGRESS/\l COMPLETED/\l NO_REPORT/\l CANCELLED\lreason: text\lobservations: text\lprice: decimal\ldeposit: decimal\lvet_fee: decimal\lpay_transaction: str\lgoogle_event_id: str\lafip_receipt_id: FK (opt)\l}", fillcolor="#FFF9C4"] + + vetvisitreport [label="{VetVisitReport|id: PK\lvisit_id: FK\lpet_id: FK\lreason: text\lphysical_exam: text\ldiagnosis: text\ltreatment: text\lpdf_file: file\l}", fillcolor="#FFF59D"] + + vetvisitfollowup [label="{VetVisitFollowUp|id: PK\lreport_id: FK\ldate: date\ldescription: text\l}", fillcolor="#FFF176"] + + vetvisitpetreason [label="{VetVisitPetReason|id: PK\lvisit_id: FK\lpet_id: FK\lreason: text\l}", fillcolor="#FFF176"] + + visit_pets [label="{M2M: Visit-Pets|vetvisit_id: FK\lpet_id: FK\l}", shape=diamond, fillcolor="#FFEE58"] + } + + // === REFERENCE DATA === + subgraph cluster_reference { + label="Reference Data" + style="rounded,filled" + fillcolor="#ECEFF1" + color="#455A64" + + specialty [label="{Specialty|id: PK\lname: str\l}", fillcolor="#CFD8DC"] + neighborhood [label="{Neighborhood|id: PK\lname: str\ldistance_coefficient: decimal\lcoverage_area: GIS Polygon\l}", fillcolor="#CFD8DC"] + province [label="{Province|id: PK\lname: str\l}", fillcolor="#CFD8DC"] + locality [label="{Locality|id: PK\lprovince_id: FK\lname: str\l}", fillcolor="#CFD8DC"] + petbreed [label="{PetBreed|id: PK\lname: str\lpet_type: DOG/CAT\l}", fillcolor="#CFD8DC"] + vaccine [label="{Vaccine|id: PK\lname: str\lpet_type: DOG/CAT\lperiodicity: int (months)\l}", fillcolor="#CFD8DC"] + study [label="{Study|id: PK\lname: str\lpet_type: DOG/CAT\lgroup_id: FK (opt)\l}", fillcolor="#CFD8DC"] + campaign [label="{Campaign|id: PK\lname: str\lutm_source: str\lutm_medium: str\lis_active: bool\l}", fillcolor="#CFD8DC"] + tag [label="{Tag|id: PK\lname: str (unique)\l}", fillcolor="#CFD8DC"] + medication [label="{Medication|id: PK\lname: str\lgeneral_name_id: FK\ltype_id: FK\lpresentation_id: FK\lpet_type: str\l}", fillcolor="#CFD8DC"] + turnfeegroup [label="{IndividualTurnFeeGroup|id: PK\lname: str\lfee_percentage: decimal\l}", fillcolor="#CFD8DC"] + } + + // === AFIP/INVOICING === + subgraph cluster_afip { + label="AFIP Invoicing" + style="rounded,filled" + fillcolor="#FCE4EC" + color="#AD1457" + + receipt [label="{Receipt|id: PK\ldocument_number: bigint\lreceipt_number: int\lissued_date: date\ltotal_amount: decimal\lnet_taxed: decimal\lcae: str\lcae_expiration: date\l}", fillcolor="#F8BBD9"] + } + + // === RELATIONSHIPS === + + // Auth -> PetOwner/Vet + auth_user -> petowner [label="1:1 opt", style=dashed, color="#2E7D32"] + auth_user -> veterinarian [label="1:1", color="#2E7D32"] + + // PetOwner relationships + petowner -> pet [label="1:N owns", color="#1565C0"] + petowner -> neighborhood [label="N:1 lives in", color="#1565C0"] + petowner -> campaign [label="N:1 opt", style=dashed, color="#666"] + petowner -> cart [label="1:N", color="#00838F"] + petowner -> servicerequest [label="1:N requests", color="#C62828"] + petowner -> vetvisit [label="1:N as owner", color="#F9A825"] + + // Pet relationships + pet -> petbreed [label="N:1 opt", style=dashed, color="#666"] + pet -> petvaccine [label="1:N", color="#1565C0"] + pet -> petstudy [label="1:N", color="#1565C0"] + petvaccine -> vaccine [label="N:1", color="#666"] + petstudy -> study [label="N:1", color="#666"] + + // Veterinarian relationships + veterinarian -> vet_specialty [label="1:N", color="#E65100"] + vet_specialty -> specialty [label="N:1", color="#E65100"] + veterinarian -> vet_neighborhood [label="1:N coverage", color="#E65100"] + vet_neighborhood -> neighborhood [label="N:1", color="#E65100"] + veterinarian -> availability [label="1:N", color="#E65100"] + veterinarian -> unavailability [label="1:N", color="#E65100"] + veterinarian -> turnfeegroup [label="N:M", color="#E65100", style=dashed] + + // Service/Pricing relationships + grupo -> category [label="1:N", color="#7B1FA2"] + category -> service [label="1:N", color="#7B1FA2"] + service -> specialty [label="N:1 opt", style=dashed, color="#7B1FA2"] + service -> prices [label="1:N", color="#7B1FA2"] + service -> discounts [label="1:N", color="#7B1FA2"] + prices -> veterinarian [label="N:1 opt\n(vet-specific)", style=dashed, color="#7B1FA2"] + + // Cart relationships + cart -> veterinarian [label="N:1 opt\n(assigned vet)", style=dashed, color="#00838F"] + cart -> cartitem [label="1:N", color="#00838F"] + cart -> cartresumeitem [label="1:N", color="#00838F"] + cart -> cartpetreason [label="1:N", color="#00838F"] + cartitem -> service [label="N:1", color="#00838F"] + cartitem -> pet [label="N:1 opt", style=dashed, color="#00838F"] + cartpetreason -> pet [label="N:1", color="#00838F"] + + // ServiceRequest relationships + servicerequest -> cart [label="1:1", color="#C62828"] + servicerequest -> veterinarian [label="N:1 opt\n(assigned)", style=dashed, color="#C62828"] + servicerequest -> statehistory [label="1:N audit", color="#C62828"] + servicerequest -> vetasked [label="1:N", color="#C62828"] + servicerequest -> payreminder [label="1:N", color="#C62828"] + servicerequest -> campaign [label="N:1 opt", style=dashed, color="#666"] + servicerequest -> tag [label="N:M", color="#666"] + vetasked -> veterinarian [label="N:1", color="#C62828"] + vetasked -> vetreminder [label="1:N", color="#C62828"] + + // VetVisit relationships + vetvisit -> servicerequest [label="1:1 opt\n(from request)", style=dashed, color="#F9A825"] + vetvisit -> veterinarian [label="N:1", color="#F9A825"] + vetvisit -> visit_pets [label="1:N", color="#F9A825"] + visit_pets -> pet [label="N:1", color="#F9A825"] + vetvisit -> vetvisitreport [label="1:N", color="#F9A825"] + vetvisit -> vetvisitpetreason [label="1:N", color="#F9A825"] + vetvisitreport -> pet [label="N:1", color="#F9A825"] + vetvisitreport -> vetvisitfollowup [label="1:N", color="#F9A825"] + vetvisitpetreason -> pet [label="N:1", color="#F9A825"] + + // AFIP + vetvisit -> receipt [label="1:1 opt\n(invoice)", style=dashed, color="#AD1457"] + + // Geography + province -> locality [label="1:N", color="#666"] +} diff --git a/atlas/book/arch-model/03-data-model.svg b/atlas/book/arch-model/03-data-model.svg new file mode 100644 index 0000000..ab40dcc --- /dev/null +++ b/atlas/book/arch-model/03-data-model.svg @@ -0,0 +1,976 @@ + + + + + + +DataModel + +AMAR Mascotas - Data Model (Entity Relationships) + +cluster_auth + +Users & Authentication + + +cluster_mascotas_owners + +Pet Owners & Pets + + +cluster_veterinarians + +Veterinarians + + +cluster_productos + +Services & Pricing + + +cluster_cart + +Cart & Checkout + + +cluster_solicitudes + +Service Requests (Workflow) + + +cluster_vetvisits + +Veterinary Visits + + +cluster_reference + +Reference Data + + +cluster_afip + +AFIP Invoicing + + + +auth_user + +auth.User + +id: PK +user name: str +email: str +is_staff: bool +is_superuser: bool + + + +petowner + +PetOwner + +id: PK +email: str (unique) +phone: str +first_name: str +last_name: str +neighborhood_id: FK +campaign_id: FK (opt) +geo_latitude: float +geo_longitude: float +address: str + + + +auth_user->petowner + + +1:1 opt + + + +veterinarian + +Veterinarian + +id: PK +user_id: FK +license: str +email: str +phone: str + + + +auth_user->veterinarian + + +1:1 + + + +pet + +Pet + +id: PK +owner_id: FK +name: str +pet_type: DOG/CAT +breed_id: FK (opt) +gender: M/F +age: int +weight: decimal +height: decimal +birth_date: date +allergies: text +neutered: bool +is_deceased: bool +state: puppy/adult/... +profile_picture: file + + + +petowner->pet + + +1:N owns + + + +cart + +Cart + +id: PK +petowner_id: FK +veterinarian_id: FK (opt) +use_vet_prices: bool +apply_turn_fee: bool + + + +petowner->cart + + +1:N + + + +servicerequest + +ServiceRequest + +id: PK +petowner_id: FK +cart_id: FK +veterinarian_id: FK (opt) +state: pending/vet_asked/ + vet_accepted/coordinated/ + payed/Confirmado/... +reason: text +days_requested: JSON +date_coordinated: datetime +hour_coordinated: time +pay_number: str +campaign_id: FK (opt) +attended_by_id: FK (opt) + + + +petowner->servicerequest + + +1:N requests + + + +vetvisit + +VetVisit + +id: PK +service_request_id: FK (opt) +owner_id: FK +veterinarian_id: FK +date: date +hour: time +visit_type: clinical/ + vaccination/ + telemedicina +visit_state: PENDING/ + IN_PROGRESS/ + COMPLETED/ + NO_REPORT/ + CANCELLED +reason: text +observations: text +price: decimal +deposit: decimal +vet_fee: decimal +pay_transaction: str +google_event_id: str +afip_receipt_id: FK (opt) + + + +petowner->vetvisit + + +1:N as owner + + + +neighborhood + +Neighborhood + +id: PK +name: str +distance_coefficient: decimal +coverage_area: GIS Polygon + + + +petowner->neighborhood + + +N:1 lives in + + + +campaign + +Campaign + +id: PK +name: str +utm_source: str +utm_medium: str +is_active: bool + + + +petowner->campaign + + +N:1 opt + + + +petvaccine + +PetVaccine + +id: PK +pet_id: FK +vaccine_id: FK +application_date: date +next_application: date + + + +pet->petvaccine + + +1:N + + + +petstudy + +PetStudy + +id: PK +pet_id: FK +study_id: FK +date: date +result: text +images: files + + + +pet->petstudy + + +1:N + + + +petbreed + +PetBreed + +id: PK +name: str +pet_type: DOG/CAT + + + +pet->petbreed + + +N:1 opt + + + +vaccine + +Vaccine + +id: PK +name: str +pet_type: DOG/CAT +periodicity: int (months) + + + +petvaccine->vaccine + + +N:1 + + + +study + +Study + +id: PK +name: str +pet_type: DOG/CAT +group_id: FK (opt) + + + +petstudy->study + + +N:1 + + + +availability + +Availability + +id: PK +veterinarian_id: FK +start_day: 0-6 +end_day: 0-6 +start_time: time +end_time: time + + + +veterinarian->availability + + +1:N + + + +unavailability + +Unavailability + +id: PK +veterinarian_id: FK +start_date: date +end_date: date +start_time: time +end_time: time +reason: str + + + +veterinarian->unavailability + + +1:N + + + +vet_specialty + +{M2M: Vet-Specialty|veterinarian_id: FK +specialty_id: FK +} + + + +veterinarian->vet_specialty + + +1:N + + + +vet_neighborhood + +{M2M: Vet-Neighborhood|veterinarian_id: FK +neighborhood_id: FK +} + + + +veterinarian->vet_neighborhood + + +1:N coverage + + + +turnfeegroup + +IndividualTurnFeeGroup + +id: PK +name: str +fee_percentage: decimal + + + +veterinarian->turnfeegroup + + +N:M + + + +specialty + +Specialty + +id: PK +name: str + + + +vet_specialty->specialty + + +N:1 + + + +vet_neighborhood->neighborhood + + +N:1 + + + +grupo + +Group + +id: PK +name: str +description: text + + + +category + +Category + +id: PK +group_id: FK +name: str +description: text +value: int (order) + + + +grupo->category + + +1:N + + + +service + +Service + +id: PK +name: str +description: text +specialty_id: FK +category_id: FK +duration: int (min) +modality: onsite/online +payment_sign_req: bool +pet_type_filter: str +age_filter: str +weight_range: str + + + +category->service + + +1:N + + + +prices + +Prices + +id: PK +service_id: FK +veterinarian_id: FK (opt) +price: decimal +professional_fee: decimal +payment_sign: decimal +from_date: date +to_date: date +active: bool + + + +service->prices + + +1:N + + + +discounts + +Discounts + +id: PK +service_id: FK +discount: decimal (%) +from_date: date +to_date: date +active: bool + + + +service->discounts + + +1:N + + + +service->specialty + + +N:1 opt + + + +prices->veterinarian + + +N:1 opt +(vet-specific) + + + +servicecombo + +ServiceCombo + +id: PK +name: str +description: text +discount_percent: decimal +discount_fixed: decimal + + + +cart->veterinarian + + +N:1 opt +(assigned vet) + + + +cartitem + +CartItem + +id: PK +cart_id: FK +pet_id: FK (opt) +service_id: FK +price: decimal +quantity: int +total: decimal (calc) + + + +cart->cartitem + + +1:N + + + +cartresumeitem + +CartResumeItem + +id: PK +cart_id: FK +concept: SUBTOTAL/ + DESCUENTO/ + ADELANTO/ + TOTAL/ + COSTO_SERVICIO +amount: decimal +order: int + + + +cart->cartresumeitem + + +1:N + + + +cartpetreason + +CartPetReason + +id: PK +cart_id: FK +pet_id: FK +reason: text + + + +cart->cartpetreason + + +1:N + + + +cartitem->pet + + +N:1 opt + + + +cartitem->service + + +N:1 + + + +cartpetreason->pet + + +N:1 + + + +servicerequest->veterinarian + + +N:1 opt +(assigned) + + + +servicerequest->cart + + +1:1 + + + +statehistory + +StateHistory + +id: PK +service_request_id: FK +state: str +additional_data: JSON +created_at: datetime +user_id: FK + + + +servicerequest->statehistory + + +1:N audit + + + +vetasked + +VeterinarianAsked + +id: PK +service_request_id: FK +veterinarian_id: FK +date_asked: datetime +date_answered: datetime +accepted: bool + + + +servicerequest->vetasked + + +1:N + + + +payreminder + +PaymentReminder + +id: PK +service_request_id: FK +scheduled_for: datetime +processed_at: datetime +status: pending/sent/... + + + +servicerequest->payreminder + + +1:N + + + +servicerequest->campaign + + +N:1 opt + + + +tag + +Tag + +id: PK +name: str (unique) + + + +servicerequest->tag + + +N:M + + + +vetasked->veterinarian + + +N:1 + + + +vetreminder + +ScheduledVetReminder + +id: PK +vet_asked_id: FK +scheduled_for: datetime +processed_at: datetime +status: pending/sent/... +celery_task_id: str + + + +vetasked->vetreminder + + +1:N + + + +vetvisit->veterinarian + + +N:1 + + + +vetvisit->servicerequest + + +1:1 opt +(from request) + + + +vetvisitreport + +VetVisitReport + +id: PK +visit_id: FK +pet_id: FK +reason: text +physical_exam: text +diagnosis: text +treatment: text +pdf_file: file + + + +vetvisit->vetvisitreport + + +1:N + + + +vetvisitpetreason + +VetVisitPetReason + +id: PK +visit_id: FK +pet_id: FK +reason: text + + + +vetvisit->vetvisitpetreason + + +1:N + + + +visit_pets + +{M2M: Visit-Pets|vetvisit_id: FK +pet_id: FK +} + + + +vetvisit->visit_pets + + +1:N + + + +receipt + +Receipt + +id: PK +document_number: bigint +receipt_number: int +issued_date: date +total_amount: decimal +net_taxed: decimal +cae: str +cae_expiration: date + + + +vetvisit->receipt + + +1:1 opt +(invoice) + + + +vetvisitreport->pet + + +N:1 + + + +vetvisitfollowup + +VetVisitFollowUp + +id: PK +report_id: FK +date: date +description: text + + + +vetvisitreport->vetvisitfollowup + + +1:N + + + +vetvisitpetreason->pet + + +N:1 + + + +visit_pets->pet + + +N:1 + + + +province + +Province + +id: PK +name: str + + + +locality + +Locality + +id: PK +province_id: FK +name: str + + + +province->locality + + +1:N + + + +medication + +Medication + +id: PK +name: str +general_name_id: FK +type_id: FK +presentation_id: FK +pet_type: str + + + diff --git a/atlas/book/arch-model/04-data-model-simple.dot b/atlas/book/arch-model/04-data-model-simple.dot new file mode 100644 index 0000000..9b4002f --- /dev/null +++ b/atlas/book/arch-model/04-data-model-simple.dot @@ -0,0 +1,195 @@ +digraph DataModelSimple { + rankdir=TB + compound=true + splines=ortho + node [shape=box, style="rounded,filled", fontname="Helvetica", fontsize=10] + edge [fontname="Helvetica", fontsize=8] + nodesep=0.3 + ranksep=1.2 + newrank=true + + label="AMAR Mascotas - Data Model Overview" + labelloc="t" + fontsize=16 + fontname="Helvetica-Bold" + + // === ROW 1: AUTH (center) === + subgraph cluster_auth { + label="Users & Auth" + style="rounded,filled" + fillcolor="#E8F5E9" + color="#2E7D32" + auth_user [label="auth.User", fillcolor="#C8E6C9"] + } + + // === ROW 2: LEFT COLUMN - Pet Owners === + subgraph cluster_mascotas { + label="Pet Owners & Pets" + style="rounded,filled" + fillcolor="#E3F2FD" + color="#1565C0" + + petowner [label="PetOwner", fillcolor="#BBDEFB"] + pet [label="Pet", fillcolor="#BBDEFB"] + petvaccine [label="PetVaccine", fillcolor="#90CAF9"] + petstudy [label="PetStudy", fillcolor="#90CAF9"] + } + + // === ROW 2: CENTER COLUMN - Veterinarians === + subgraph cluster_vets { + label="Veterinarians" + style="rounded,filled" + fillcolor="#FFF3E0" + color="#E65100" + + veterinarian [label="Veterinarian", fillcolor="#FFE0B2"] + availability [label="Availability", fillcolor="#FFCC80"] + unavailability [label="Unavailability", fillcolor="#FFCC80"] + vet_specialty [label="Vet-Specialty", shape=diamond, fillcolor="#FFB74D"] + vet_neighborhood [label="Vet-Neighborhood", shape=diamond, fillcolor="#FFB74D"] + } + + // === ROW 2: RIGHT COLUMN - Services === + subgraph cluster_productos { + label="Services & Pricing" + style="rounded,filled" + fillcolor="#F3E5F5" + color="#7B1FA2" + + grupo [label="Group", fillcolor="#E1BEE7"] + category [label="Category", fillcolor="#E1BEE7"] + service [label="Service", fillcolor="#CE93D8"] + prices [label="Prices", fillcolor="#BA68C8"] + discounts [label="Discounts", fillcolor="#BA68C8"] + } + + // === ROW 3: LEFT - Cart === + subgraph cluster_cart { + label="Cart & Checkout" + style="rounded,filled" + fillcolor="#E0F7FA" + color="#00838F" + + cart [label="Cart", fillcolor="#B2EBF2"] + cartitem [label="CartItem", fillcolor="#80DEEA"] + cartresumeitem [label="CartResumeItem", fillcolor="#80DEEA"] + cartpetreason [label="CartPetReason", fillcolor="#80DEEA"] + } + + // === ROW 3: CENTER - Requests === + subgraph cluster_solicitudes { + label="Service Requests" + style="rounded,filled" + fillcolor="#FFEBEE" + color="#C62828" + + servicerequest [label="ServiceRequest", fillcolor="#FFCDD2"] + statehistory [label="StateHistory", fillcolor="#EF9A9A"] + vetasked [label="VeterinarianAsked", fillcolor="#EF9A9A"] + vetreminder [label="VetReminder", fillcolor="#E57373"] + payreminder [label="PayReminder", fillcolor="#E57373"] + } + + // === ROW 3: RIGHT - Visits === + subgraph cluster_visits { + label="Veterinary Visits" + style="rounded,filled" + fillcolor="#FFFDE7" + color="#F9A825" + + vetvisit [label="VetVisit", fillcolor="#FFF9C4"] + vetvisitreport [label="VetVisitReport", fillcolor="#FFF59D"] + vetvisitfollowup [label="FollowUp", fillcolor="#FFF176"] + vetvisitpetreason [label="VisitPetReason", fillcolor="#FFF176"] + visit_pets [label="Visit-Pets", shape=diamond, fillcolor="#FFEE58"] + receipt [label="Receipt\n(AFIP)", fillcolor="#F8BBD9"] + } + + // === ROW 4: REFERENCE DATA (bottom, full width) === + subgraph cluster_reference { + label="Reference Data" + style="rounded,filled" + fillcolor="#ECEFF1" + color="#455A64" + + subgraph { + rank=same + specialty [label="Specialty", fillcolor="#CFD8DC"] + neighborhood [label="Neighborhood", fillcolor="#CFD8DC"] + province [label="Province", fillcolor="#CFD8DC"] + locality [label="Locality", fillcolor="#CFD8DC"] + petbreed [label="PetBreed", fillcolor="#CFD8DC"] + vaccine [label="Vaccine", fillcolor="#CFD8DC"] + } + subgraph { + rank=same + study [label="Study", fillcolor="#CFD8DC"] + campaign [label="Campaign", fillcolor="#CFD8DC"] + tag [label="Tag", fillcolor="#CFD8DC"] + medication [label="Medication", fillcolor="#CFD8DC"] + turnfeegroup [label="TurnFeeGroup", fillcolor="#CFD8DC"] + } + specialty -> study [style=invis] + province -> locality + } + + // === FORCE COLUMN ALIGNMENT WITH INVISIBLE EDGES === + // Column 1: Pets -> Cart + petowner -> cart [style=invis, weight=10] + // Column 2: Vets -> Requests + veterinarian -> servicerequest [style=invis, weight=10] + // Column 3: Services -> Visits + service -> vetvisit [style=invis, weight=10] + + // Force Reference Data to bottom + cart -> specialty [style=invis, weight=10] + servicerequest -> campaign [style=invis, weight=10] + vetvisit -> turnfeegroup [style=invis, weight=10] + + // === INTERNAL CLUSTER EDGES === + petowner -> pet [label="1:N"] + pet -> petvaccine [label="1:N"] + pet -> petstudy [label="1:N"] + + veterinarian -> availability + veterinarian -> unavailability + veterinarian -> vet_specialty + veterinarian -> vet_neighborhood + + grupo -> category [label="1:N"] + category -> service [label="1:N"] + service -> prices [label="1:N"] + service -> discounts [label="1:N"] + + cart -> cartitem [label="1:N"] + cart -> cartresumeitem + cart -> cartpetreason + + servicerequest -> statehistory [label="1:N"] + servicerequest -> vetasked [label="1:N"] + servicerequest -> payreminder + vetasked -> vetreminder + + vetvisit -> visit_pets + vetvisit -> vetvisitreport [label="1:N"] + vetvisit -> vetvisitpetreason + vetvisitreport -> vetvisitfollowup + vetvisit -> receipt [style=dashed] + + // === CROSS-CLUSTER RELATIONSHIPS === + auth_user -> petowner [label="1:1 opt", style=dashed] + auth_user -> veterinarian [label="1:1"] + + petowner -> cart [label="1:N"] + petowner -> servicerequest [label="1:N"] + servicerequest -> cart [label="1:1", constraint=false] + vetasked -> veterinarian [constraint=false] + vetvisit -> servicerequest [style=dashed, constraint=false] + + // Reference links + petvaccine -> vaccine [constraint=false] + petstudy -> study [constraint=false] + vet_specialty -> specialty [constraint=false] + vet_neighborhood -> neighborhood [constraint=false] + cartitem -> service [constraint=false] +} diff --git a/atlas/book/arch-model/04-data-model-simple.svg b/atlas/book/arch-model/04-data-model-simple.svg new file mode 100644 index 0000000..601d83b --- /dev/null +++ b/atlas/book/arch-model/04-data-model-simple.svg @@ -0,0 +1,540 @@ + + + + + + +DataModelSimple + +AMAR Mascotas - Data Model Overview + +cluster_auth + +Users & Auth + + +cluster_mascotas + +Pet Owners & Pets + + +cluster_vets + +Veterinarians + + +cluster_productos + +Services & Pricing + + +cluster_cart + +Cart & Checkout + + +cluster_solicitudes + +Service Requests + + +cluster_visits + +Veterinary Visits + + +cluster_reference + +Reference Data + + + +auth_user + +auth.User + + + +petowner + +PetOwner + + + +auth_user->petowner + + +1:1 opt + + + +veterinarian + +Veterinarian + + + +auth_user->veterinarian + + +1:1 + + + +pet + +Pet + + + +petowner->pet + + +1:N + + + +cart + +Cart + + + + +petowner->cart + + +1:N + + + +servicerequest + +ServiceRequest + + + +petowner->servicerequest + + +1:N + + + +petvaccine + +PetVaccine + + + +pet->petvaccine + + +1:N + + + +petstudy + +PetStudy + + + +pet->petstudy + + +1:N + + + +vaccine + +Vaccine + + + +petvaccine->vaccine + + + + + +study + +Study + + + +petstudy->study + + + + + +availability + +Availability + + + +veterinarian->availability + + + + + +unavailability + +Unavailability + + + +veterinarian->unavailability + + + + + +vet_specialty + +Vet-Specialty + + + +veterinarian->vet_specialty + + + + + +vet_neighborhood + +Vet-Neighborhood + + + +veterinarian->vet_neighborhood + + + + + + +specialty + +Specialty + + + +vet_specialty->specialty + + + + + +neighborhood + +Neighborhood + + + +vet_neighborhood->neighborhood + + + + + +grupo + +Group + + + +category + +Category + + + +grupo->category + + +1:N + + + +service + +Service + + + +category->service + + +1:N + + + +prices + +Prices + + + +service->prices + + +1:N + + + +discounts + +Discounts + + + +service->discounts + + +1:N + + + +vetvisit + +VetVisit + + + + +cartitem + +CartItem + + + +cart->cartitem + + +1:N + + + +cartresumeitem + +CartResumeItem + + + +cart->cartresumeitem + + + + + +cartpetreason + +CartPetReason + + + +cart->cartpetreason + + + + + + +cartitem->service + + + + + +servicerequest->cart + + +1:1 + + + +statehistory + +StateHistory + + + +servicerequest->statehistory + + +1:N + + + +vetasked + +VeterinarianAsked + + + +servicerequest->vetasked + + +1:N + + + +payreminder + +PayReminder + + + +servicerequest->payreminder + + + + + +campaign + +Campaign + + + + +vetasked->veterinarian + + + + + +vetreminder + +VetReminder + + + +vetasked->vetreminder + + + + + +vetvisit->servicerequest + + + + + +vetvisitreport + +VetVisitReport + + + +vetvisit->vetvisitreport + + +1:N + + + +vetvisitpetreason + +VisitPetReason + + + +vetvisit->vetvisitpetreason + + + + + +visit_pets + +Visit-Pets + + + +vetvisit->visit_pets + + + + + +receipt + +Receipt +(AFIP) + + + +vetvisit->receipt + + + + + +turnfeegroup + +TurnFeeGroup + + + + +vetvisitfollowup + +FollowUp + + + +vetvisitreport->vetvisitfollowup + + + + + + +province + +Province + + + +locality + +Locality + + + +province->locality + + + + + +petbreed + +PetBreed + + + +tag + +Tag + + + +medication + +Medication + + + diff --git a/atlas/book/arch-model/graph.html b/atlas/book/arch-model/graph.html new file mode 100644 index 0000000..999c0f1 --- /dev/null +++ b/atlas/book/arch-model/graph.html @@ -0,0 +1,120 @@ + + + + + + Graph Viewer - AMAR Mascotas + + + +
+ ← Index + +

Loading...

+
+ + + + + +
+
+ +
+ Graph +
+ + + + diff --git a/atlas/book/arch-model/index.html b/atlas/book/arch-model/index.html new file mode 100644 index 0000000..9c88bb0 --- /dev/null +++ b/atlas/book/arch-model/index.html @@ -0,0 +1,245 @@ + + + + + + AMAR Mascotas - Architecture & Data Models + + + +
+

AMAR Mascotas

+

Architecture & Data Model Documentation

+
+ +
+ +
+
+

Data Model Overview

+ View Full +
+ + Data Model Overview + +
+

High-level entity relationships without field details. Shows the main actors, workflow, and data flow.

+

Clusters

+
    +
  • Users & Auth: Django auth.User as central identity
  • +
  • Pet Owners & Pets: Clients, their pets, vaccines, studies
  • +
  • Veterinarians: Vets with availability, specialties, coverage areas
  • +
  • Services & Pricing: Service catalog with dynamic pricing
  • +
  • Cart & Checkout: Shopping cart workflow
  • +
  • Service Requests: Order lifecycle with state machine
  • +
  • Veterinary Visits: Scheduled visits, reports, AFIP invoicing
  • +
  • Reference Data: Lookups (specialties, neighborhoods, vaccines, etc.)
  • +
+
+
+ +
+
+

Backend Architecture

+ View Full +
+ + Backend Architecture + +
+

Django apps structure: mascotas, productos, solicitudes, common, payments, and external integrations.

+ +

Celery Tasks

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TaskAppPurpose
send_veterinarian_followupsolicitudesRe-sends availability request to vet if still pending
run_payment_reminder_cronsolicitudesCron job to send payment reminders
create_vetvisit_in_sheetmascotasCreates row in Google Sheets for visit
update_vetvisit_in_sheetmascotasUpdates Google Sheets row
create_event_calendar_vetvisitmascotasCreates Google Calendar event
update_event_calendar_vetvisitmascotasUpdates Google Calendar event
create_user_ownermascotasCreates Django user for PetOwner + welcome email
generate_vetvisit_invoicemascotasGenerates AFIP invoice and PDF
fetch_mp_notification_detailspaymentsFetches MercadoPago webhook details
+

Celery handles async operations: external APIs (Google, MercadoPago, AFIP), scheduled reminders, and heavy processing (invoices, emails).

+
+
+ +
+
+

Frontend Architecture

+ View Full +
+ + Frontend Architecture + +
+

Next.js 13+ App Router structure with public pages, backoffice, shared components, and services layer.

+

Key Areas

+
    +
  • Public Pages: Landing, service catalog, booking flow
  • +
  • Backoffice: Role-based dashboards (admin, vet, petowner)
  • +
  • Services Layer: API clients for backend communication
  • +
  • State Management: Redux store for cart, auth, UI state
  • +
  • Shared Components: Forms, tables, modals, navigation
  • +
+
+
+ +
+
+

Detailed Data Model

+ View Full +
+ + Detailed Data Model + +
+

Complete entity-relationship diagram with all fields, types, and relationships.

+

Data Patterns

+
    +
  • Soft delete: deleted flag on most models
  • +
  • Audit trail: StateHistory for service requests
  • +
  • Geographic: PostGIS polygons for coverage areas
  • +
  • Versioned pricing: Date ranges on Prices
  • +
+
+
+ + +
+

Key Findings

+ +
+
+

User Types

+

All users connect to auth.User:

+
    +
  • PetOwner: Optional 1:1 link (created lazily)
  • +
  • Veterinarian: Required 1:1 link to User
  • +
  • Staff: Direct Django users with is_staff=True
  • +
+
+ +
+

Core Workflow

+
+ PetOwner → Cart → ServiceRequest → VeterinarianAsked → VetVisit → Report +
+

State Machine:

+
+ pending → vet_asked → vet_accepted → coordinated → payed → Confirmado +
+
+ +
+

Pricing Logic

+
    +
  • Base price × neighborhood.distance_coefficient
  • +
  • Optional vet-specific pricing via Prices.veterinarian_id
  • +
  • Turn fee surcharge via IndividualTurnFeeGroup
  • +
  • Time-based discounts via Discounts model
  • +
+
+ +
+

External Integrations

+
    +
  • MercadoPago: Payment processing
  • +
  • Google Calendar: Visit synchronization
  • +
  • Google Sheets: Visit tracking spreadsheet
  • +
  • Mercately: WhatsApp notifications
  • +
  • AFIP: Argentine tax invoicing
  • +
+
+
+
+ + +
+

Technology Stack

+
+
+

Backend

+
    +
  • Django 4.x
  • +
  • Django REST Framework
  • +
  • PostgreSQL + PostGIS
  • +
  • Celery (Redis)
  • +
  • JWT Authentication
  • +
  • django-afip
  • +
+
+
+

Frontend

+
    +
  • Next.js 13+ (App Router)
  • +
  • React 18+
  • +
  • TypeScript
  • +
  • Redux
  • +
  • Axios
  • +
  • Tailwind CSS
  • +
+
+
+

Infrastructure

+
    +
  • Docker
  • +
  • Nginx
  • +
  • AWS S3 (storage)
  • +
  • MercadoPago API
  • +
  • Google APIs
  • +
  • WhatsApp Business
  • +
+
+
+
+
+ +
+

AMAR Mascotas Architecture Documentation

+

Generated:

+
+ + diff --git a/atlas/book/arch-model/styles.css b/atlas/book/arch-model/styles.css new file mode 100644 index 0000000..851ca04 --- /dev/null +++ b/atlas/book/arch-model/styles.css @@ -0,0 +1,565 @@ +/* Reset and base */ +*, *::before, *::after { + box-sizing: border-box; +} + +:root { + --color-bg: #0f0f0f; + --color-surface: #1a1a1a; + --color-surface-hover: #252525; + --color-border: #333; + --color-text: #e0e0e0; + --color-text-muted: #888; + --color-accent: #4A90D9; + --color-accent-light: #6BA3E0; + --radius: 8px; + --shadow: 0 2px 8px rgba(0,0,0,0.3); +} + +html { + font-size: 16px; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, sans-serif; + background: var(--color-bg); + color: var(--color-text); + line-height: 1.6; + margin: 0; + padding: 0; + min-height: 100vh; +} + +/* Header */ +header { + background: var(--color-surface); + border-bottom: 1px solid var(--color-border); + padding: 2rem; + text-align: center; +} + +header h1 { + margin: 0; + font-size: 2rem; + font-weight: 600; + color: var(--color-text); +} + +header .subtitle { + margin: 0.5rem 0 0; + color: var(--color-text-muted); + font-size: 1rem; +} + +/* Main content */ +main { + max-width: 1400px; + margin: 0 auto; + padding: 2rem; +} + +section { + margin-bottom: 3rem; +} + +section h2 { + font-size: 1.5rem; + font-weight: 600; + margin: 0 0 1.5rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid var(--color-border); +} + +/* Card Grid - max 3 columns */ +.card-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 1.5rem; +} + +@media (min-width: 1000px) { + .card-grid { + grid-template-columns: repeat(3, 1fr); + } +} + +@media (min-width: 700px) and (max-width: 999px) { + .card-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +/* Graph Cards */ +.card { + display: block; + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius); + overflow: hidden; + text-decoration: none; + color: inherit; + transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s; +} + +.card:hover { + transform: translateY(-2px); + box-shadow: var(--shadow); + border-color: var(--color-accent); +} + +.card-preview { + aspect-ratio: 4/3; + background: #fff; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + padding: 1rem; +} + +.card-preview img { + max-width: 100%; + max-height: 100%; + object-fit: contain; +} + +.card-content { + padding: 1rem; +} + +.card-content h3 { + margin: 0 0 0.5rem; + font-size: 1.1rem; + font-weight: 600; + color: var(--color-accent-light); +} + +.card-content p { + margin: 0; + font-size: 0.9rem; + color: var(--color-text-muted); +} + +/* Findings Grid */ +.findings-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 1.5rem; +} + +@media (min-width: 1000px) { + .findings-grid { + grid-template-columns: repeat(3, 1fr); + } +} + +.finding-card { + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius); + padding: 1.25rem; +} + +.finding-card h3 { + margin: 0 0 0.75rem; + font-size: 1rem; + font-weight: 600; + color: var(--color-accent-light); +} + +.finding-card p, +.finding-card ul { + margin: 0 0 0.75rem; + font-size: 0.9rem; +} + +.finding-card ul { + padding-left: 1.25rem; +} + +.finding-card li { + margin-bottom: 0.25rem; +} + +.finding-card code { + background: #2a2a2a; + padding: 0.1em 0.4em; + border-radius: 3px; + font-size: 0.85em; + color: #f0f0f0; +} + +.workflow-diagram { + background: #2a2a2a; + padding: 0.75rem; + border-radius: 4px; + margin-bottom: 0.75rem; + overflow-x: auto; +} + +.workflow-diagram code { + background: none; + padding: 0; + font-size: 0.8rem; + white-space: nowrap; +} + +.state-flow { + font-family: monospace; + font-size: 0.8rem; + color: var(--color-text-muted); + word-break: break-all; +} + +.stats-table { + width: 100%; + font-size: 0.85rem; + border-collapse: collapse; +} + +.stats-table td { + padding: 0.35rem 0; + border-bottom: 1px solid var(--color-border); +} + +.stats-table td:first-child { + font-family: monospace; + color: var(--color-accent-light); +} + +.stats-table td:last-child { + color: var(--color-text-muted); + text-align: right; +} + +/* Tech Stack Grid */ +.tech-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1.5rem; +} + +@media (min-width: 700px) { + .tech-grid { + grid-template-columns: repeat(3, 1fr); + } +} + +.tech-column { + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius); + padding: 1.25rem; +} + +.tech-column h3 { + margin: 0 0 0.75rem; + font-size: 1rem; + font-weight: 600; + color: var(--color-accent-light); +} + +.tech-column ul { + margin: 0; + padding-left: 1.25rem; + font-size: 0.9rem; +} + +.tech-column li { + margin-bottom: 0.25rem; +} + +/* Footer */ +footer { + background: var(--color-surface); + border-top: 1px solid var(--color-border); + padding: 1.5rem 2rem; + text-align: center; + color: var(--color-text-muted); + font-size: 0.9rem; +} + +footer p { + margin: 0.25rem 0; +} + +/* Graph Viewer Page */ +.graph-viewer { + display: flex; + flex-direction: column; + min-height: 100vh; +} + +.graph-header { + background: var(--color-surface); + border-bottom: 1px solid var(--color-border); + padding: 1rem 2rem; + display: flex; + align-items: center; + gap: 1rem; + flex-shrink: 0; +} + +.graph-header .back-link { + color: var(--color-accent); + text-decoration: none; + font-size: 0.9rem; +} + +.graph-header .back-link:hover { + text-decoration: underline; +} + +.nav-controls { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.nav-controls button { + background: var(--color-surface-hover); + border: 1px solid var(--color-border); + color: var(--color-text); + padding: 0.4rem 0.7rem; + border-radius: 4px; + cursor: pointer; + font-size: 0.9rem; +} + +.nav-controls button:hover:not(:disabled) { + background: var(--color-border); +} + +.nav-controls button:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.nav-controls span { + font-size: 0.85rem; + color: var(--color-text-muted); + min-width: 3rem; + text-align: center; +} + +.graph-header h1 { + margin: 0; + font-size: 1.25rem; + font-weight: 600; + flex: 1; +} + +.graph-controls { + display: flex; + gap: 0.5rem; +} + +.graph-controls button { + background: var(--color-surface-hover); + border: 1px solid var(--color-border); + color: var(--color-text); + padding: 0.5rem 1rem; + border-radius: 4px; + cursor: pointer; + font-size: 0.85rem; +} + +.graph-controls button:hover { + background: var(--color-border); +} + +.graph-container { + flex: 1; + overflow: auto; + background: #fff; + display: flex; + align-items: center; + justify-content: center; + padding: 2rem; +} + +.graph-container img { + max-width: 100%; + max-height: 100%; + object-fit: contain; +} + +.graph-container.fit-width img { + width: 100%; + height: auto; + max-height: none; +} + +.graph-container.fit-height img { + height: calc(100vh - 80px); + width: auto; + max-width: none; +} + +.graph-container.actual-size img { + max-width: none; + max-height: none; +} + +/* Graph Sections (index page) */ +.graph-section { + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius); + margin-bottom: 2rem; + overflow: hidden; +} + +.graph-header-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 1.5rem; + border-bottom: 1px solid var(--color-border); +} + +.graph-header-row h2 { + margin: 0; + padding: 0; + border: none; + font-size: 1.25rem; +} + +.view-btn { + background: var(--color-accent); + color: #fff; + text-decoration: none; + padding: 0.5rem 1rem; + border-radius: 4px; + font-size: 0.85rem; + font-weight: 500; +} + +.view-btn:hover { + background: var(--color-accent-light); +} + +.graph-section .graph-preview { + display: block; + background: #fff; + max-height: 400px; + overflow: hidden; +} + +.graph-section .graph-preview img { + width: 100%; + height: auto; + object-fit: contain; + object-position: top left; +} + +.graph-details { + padding: 1.5rem; + border-top: 1px solid var(--color-border); +} + +.graph-details p { + margin: 0 0 1rem; + color: var(--color-text-muted); +} + +.graph-details h4 { + margin: 1.5rem 0 0.75rem; + font-size: 0.95rem; + font-weight: 600; + color: var(--color-accent-light); +} + +.graph-details h4:first-child { + margin-top: 0; +} + +.graph-details ul { + margin: 0; + padding-left: 1.25rem; + font-size: 0.9rem; +} + +.graph-details li { + margin-bottom: 0.35rem; +} + +.graph-details code { + background: #2a2a2a; + padding: 0.1em 0.4em; + border-radius: 3px; + font-size: 0.85em; +} + +/* Details Table */ +.details-table { + width: 100%; + border-collapse: collapse; + font-size: 0.85rem; + margin: 0.5rem 0 1rem; +} + +.details-table th { + text-align: left; + padding: 0.5rem; + background: #2a2a2a; + border-bottom: 1px solid var(--color-border); + font-weight: 600; + color: var(--color-text); +} + +.details-table td { + padding: 0.5rem; + border-bottom: 1px solid var(--color-border); + vertical-align: top; +} + +.details-table code { + background: #2a2a2a; + padding: 0.15em 0.4em; + border-radius: 3px; + font-size: 0.85em; + white-space: nowrap; +} + +.note { + font-size: 0.85rem; + color: var(--color-text-muted); + font-style: italic; + margin-top: 0.5rem; +} + +/* Mobile adjustments */ +@media (max-width: 600px) { + main { + padding: 1rem; + } + + header { + padding: 1.5rem 1rem; + } + + header h1 { + font-size: 1.5rem; + } + + .card-grid, + .findings-grid { + grid-template-columns: 1fr; + } + + .graph-header { + flex-wrap: wrap; + padding: 1rem; + } + + .graph-header h1 { + order: -1; + width: 100%; + margin-bottom: 0.5rem; + } + + .graph-controls { + flex-wrap: wrap; + } +} diff --git a/atlas/book/drive-index/index.html b/atlas/book/drive-index/index.html new file mode 100644 index 0000000..012e862 --- /dev/null +++ b/atlas/book/drive-index/index.html @@ -0,0 +1,300 @@ + + + + + + Drive Index - Amar Mascotas + + + +
+ +
+

Drive Index

+

Amar Mascotas - Company Drive Structure

+
+ 12 folders + ~235 files +
+
+ +
+ Preview Mode + This shows the Drive folder structure with placeholder filenames. Real file data will be available once Google Vein (Google Drive sync) is implemented in Artery: +
    +
  • Auto-sync folder structure and file metadata
  • +
  • Direct links to files (requires Google Workspace login)
  • +
  • Live updates when Drive contents change
  • +
+
+ +
+ 01. Identidad3 files +
+
Brand assets, logos, typography
+
+
png logo_main.png
+
png logo_alt.png
+
ttf brand_font.ttf
+
+
+
+ +
+ 02. Marketing Contenidos8 files +
+
Social media content, templates, testimonials
+
+
docx social_template.docx
+
png post_image1.png
+
png post_image2.png
+
mov event_video.mov
+
png testimonials.png
+
+
+
+ +
+ 03. Marketing Growth8 files +
+
Paid media campaigns, performance dashboards
+
+
xlsx dashboard_report.xlsx
+
png ad_1080x1080.png
+
png ad_1200x628.png
+
png ad_stories.png
+
+
+
+ +
+ 05. ATC - Operaciones12 files +
+
Process manuals, workflows, support docs
+
+
docx manual_procesos.docx
+
jpg workflow_diagram.jpg
+
xlsx contacts_list.xlsx
+
xlsx postal_codes.xlsx
+
xlsx tasks_tracker.xlsx
+
+
+
+ +
+ 06. Supply7 files +
+
Vet network, lab partnerships, onboarding
+
+
pdf lab_prices.pdf
+
xlsx vet_onboarding.xlsx
+
pdf vet_cv_sample.pdf
+
mp4 meeting_recording.mp4
+
+
+
+ +
+ 07. Finanzas7 files +
+
Invoices, accounting, finance tracking
+
+
xlsx finance_tracker.xlsx
+
pdf invoice_may.pdf
+
pdf invoice_jun.pdf
+
pdf invoice_jul.pdf
+
+
+
+ +
+ 08. IT y Producto57 files +
+
Product specs, feature docs, references
+
+
docx product_index.docx
+
docx feature_specs.docx
+
xlsx platform_states.xlsx
+
xlsx medications_db.xlsx
+
xlsx events_mapping.xlsx
+
pptx benchmark.pptx
+
+
+
+ +
+ 09. DATA2 files +
+
Database exports, data backups
+
+
xlsx pets_database.xlsx
+
xlsx pets_backup.xlsx
+
+
+
+ +
+ Clientes - ventas22 files +
+
CRM exports, sales data, client info
+
+
xlsx crm_export.xlsx
+
xlsx churn_analysis.xlsx
+
xlsx leads_2023.xlsx
+
xlsx leads_2024.xlsx
+
pdf monthly_report.pdf
+
+
+
+ +
+ HR17 files +
+
Onboarding, job profiles, team docs
+
+
pptx onboarding_deck.pptx
+
docx job_profile_sales.docx
+
pdf job_profile_growth.pdf
+
jpg team_photo.jpg
+
+
+
+ +
+ Pitch Decks24 files +
+
Investor presentations, company decks
+
+
pptx pitch_deck_latest.pptx
+
docx video_script.docx
+
pptx product_timeline.pptx
+
pptx investor_update.pptx
+
+
+
+ +
+ z.Backup archivos viejos46 files +
+
Legacy files, old exports
+
+
xlsx old_vets_list.xlsx
+
xlsx org_chart.xlsx
+
xlsx data_room_2023.xlsx
+
xlsx legacy_export.xlsx
+
+
+
+ +
pawprint/album
+
+ + diff --git a/atlas/book/drive-index/index.md b/atlas/book/drive-index/index.md new file mode 100644 index 0000000..fa36e5e --- /dev/null +++ b/atlas/book/drive-index/index.md @@ -0,0 +1,217 @@ +# Drive Index - Amar Mascotas + +> vault source: `vault/drive/` +> 48 files | Last indexed: 2025-12-08 + +## Overview by Area + +| Area | Focus | Key Files | Workflow Potential | +|------|-------|-----------|-------------------| +| 01. Identidad | Brand assets | Illustrations, Typography | Design handoff | +| 02. Marketing Contenidos | Social/Content | Posts, Event media, Templates | Content calendar | +| 03. Marketing Growth | Paid/Performance | Ad creatives, Dashboards | Campaign tracking | +| 05. ATC - Operaciones | Ops/Support | Process manual, Workflow diagram | Onboarding, SOP | +| 06. Supply | Vets/Labs | Vet tracking, Lab prices, CVs | Vet onboarding | +| 07. Finanzas | Accounting | Invoices, Finance sheet | Reconciliation | +| Clientes | Sales/CRM | Fitsales export | Client tracking | +| Pitch Decks | Presentations | Company pitch | Investor/partner | + +--- + +## 01. Identidad Amar Mascotas +Brand identity assets. + +``` +Ilustraciones amar PNG/ +├── Asset 4@4x.png +└── Asset 11@4x.png + +Tipografia/Open_Sans/static/ +└── OpenSans_Condensed-Italic.ttf +``` + +**Workflow relevance**: Design system, brand consistency checks. + +--- + +## 02. Marketing Contenidos +Social media content and production assets. + +``` +Backup archivos viejos/ +├── Contenido evento amar 10 ago 2023/ +│ ├── IMG_7309.MOV +│ └── video-output-(...).mov +└── RRSS MSJ PREDETERMINADOS.docx ← Predefined social messages + +Produccion audiovisual Jul 24/ +└── Fotos Produccion Audiovisual JUL24/ + └── Vacunacion Fotos Produccion JUL 24/ + └── IMG_4620.JPG + +Redes sociales Amar/ +├── 02. FEBRERO 25 Finales/12-2/cambios/247.png +├── 08. AGOSTO 24 Finales Amar/ +│ ├── 13.8/1.png +│ └── 21.8/3.png +└── Comentarios clientes felices JUN-JUL24/ + └── Captura de pantalla (...).png ← Testimonials +``` + +**Workflow relevance**: +- `RRSS MSJ PREDETERMINADOS.docx` = template for ATC/social responses +- Monthly folders suggest content calendar exists +- Testimonials capture = social proof workflow + +--- + +## 03. Marketing Growth +Paid media campaigns and performance tracking. + +``` +Paid Media - Pauta digital/ +├── 05.MAY24 - ADS Testimonios/ +├── 08.AGO24 - ADS 10 OFF/ +│ ├── 1080 x 1080-17.png +│ ├── 1200 x 628-32.png +│ ├── 1200 x 628-33.png +│ ├── 1200 x 628-38.png +│ ├── 1200 x 628-39.png +│ └── 960 x 1200-30.png +├── Amar Mascotas - Reporte_Dash general(...)(2).xlsx +└── Amar Mascotas - Reporte_Dash general(...)(3).xlsx +``` + +**Workflow relevance**: +- Ad size variants = multi-platform campaigns (Meta, Google) +- Dashboard reports = performance tracking exists +- Monthly campaign folders = structured campaign workflow + +--- + +## 05. ATC - Operaciones +Operations, customer service, and legacy data. + +``` +Operaciones backup viejos/ +├── ATC - Manual de Procesos.docx ← KEY: Process documentation +├── work Flow servicio veterinario.jpg ← KEY: Service workflow diagram +├── Clientes a contactar.xlsx +├── codigos_postales_y_barrios (1).xlsx ← Coverage/delivery zones +├── domesticables ultimo.docx +├── domesticables ultimo.pdf +├── INFO.xlsx +├── Reporte DOMESTICABLES 11_5.xlsx +├── Tareas de Leila.xlsx +└── entrega/ + ├── prestashop/ + │ ├── clientes a escribir febrero 2023.csv + │ └── datos web vieja/ + │ └── reembolsos por falta de stock.csv + └── vendedores/somos pam/ + └── articulos desactivados en marzo 2023.csv +``` + +**Workflow relevance**: +- `ATC - Manual de Procesos.docx` = existing SOP (Gherkin source?) +- `work Flow servicio veterinario.jpg` = visual process (BDD source?) +- Legacy CSV = migration/history context +- Task tracking = ops workflow exists + +--- + +## 06. Supply (vetes-labo-clinicas) +Veterinarian network and lab partnerships. + +``` +Comunciacion/ +└── Veterinarios-46.jpg + +Formularios- Viejos - no usar/ +└── Reuniones Matias/ + ├── Grabación de pantalla 2024-08-20(...).mp4 + └── Grabación de pantalla 2024-08-21(...).mp4 + +Laboratorios/DiagnoTest/ +└── Diagnotest-ListaPrecios-Junio2025 (1).pdf ← Current pricing + +Veterinarios postulantes/ +├── Aldana Becker - Colaboracion Amar Mascotas.docx +├── Ana Christophersen/ +│ └── CV - Ana Christophersen.pdf +└── Ingreso Veterinarios.xlsx ← Vet onboarding tracker +``` + +**Workflow relevance**: +- `Ingreso Veterinarios.xlsx` = vet recruitment/onboarding flow +- Lab price list = service pricing reference +- Meeting recordings = training/knowledge transfer + +--- + +## 07. Finanzas y contabilidad +Invoices and financial tracking. + +``` +Facturas/ +├── May 23/Recibido/ +│ ├── FacturaAMARMASCOTASSA(...).pdf +│ └── FacturaAMARMASCOTASSA(...).pdf +├── Jun 23/Recibido/ +│ ├── nc 4-29.pdf +│ └── nc 4-30.pdf +└── Jul 23/Recibido/ + ├── FAC 567 ZUCCONI ALAN EDGARDO.pdf + └── Mercaba carlos roberto cozak.pdf + +Finanzas Amar mascotas.xlsx ← Main finance tracker +``` + +**Workflow relevance**: +- Monthly invoice organization = accounting workflow +- Finance xlsx = reconciliation process + +--- + +## Clientes - ventas - devoluciones +CRM and sales data. + +``` +Excels Clientes - ventas - devoluciones/ +└── Fitsales CRM vinculados hasta el 06 dic.xlsx +``` + +**Workflow relevance**: +- CRM export = client tracking, sales funnel + +--- + +## Pitch Decks - Presentaciones +Company presentations. + +``` +Copias Pitch Deck Cata/ +└── Copia de Pitch Deck - ABR 2025.pptx +``` + +**Workflow relevance**: +- Investor/partner communications + +--- + +## Workflow Discovery Summary + +### Documents to Review for BDD/Gherkin +1. `05. ATC/ATC - Manual de Procesos.docx` - existing process documentation +2. `05. ATC/work Flow servicio veterinario.jpg` - visual workflow +3. `02. Marketing/RRSS MSJ PREDETERMINADOS.docx` - response templates + +### Active Tracking Sheets +- `06. Supply/Ingreso Veterinarios.xlsx` - vet onboarding +- `Clientes/Fitsales CRM vinculados.xlsx` - client tracking +- `07. Finanzas/Finanzas Amar mascotas.xlsx` - finance + +### Potential Artery Connections +- Fitsales CRM → pulse for client data +- Finance xlsx → pulse for accounting +- Vet tracker → pulse for supply management diff --git a/atlas/book/feature-flow/CLAUDE.md b/atlas/book/feature-flow/CLAUDE.md new file mode 100644 index 0000000..1395564 --- /dev/null +++ b/atlas/book/feature-flow/CLAUDE.md @@ -0,0 +1,60 @@ +# Feature Flow Book + +## Purpose +Presentation showing the feature standardization pipeline. + +## The Pipeline + +``` +┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ +│ OPS TEMPLATES │ -> │ BDD/GHERKIN │ -> │ TESTS │ +│ │ │ │ │ │ +│ Non-technical │ │ .feature files │ │ Backend: │ +│ User flows │ │ Given/When/Then │ │ API contracts │ +│ From support │ │ Human readable │ │ Workflows │ +│ │ │ │ │ Frontend: │ +│ │ │ │ │ Page Objects │ +│ │ │ │ │ E2E specs │ +└──────────────────┘ └──────────────────┘ └──────────────────┘ +``` + +## Files +- `index-en.html` - English slide presentation (8 slides, arrow keys) +- `index-es.html` - Spanish slide presentation (8 slides, arrow keys) + +## Slides Structure +1. Title +2. Pipeline Overview (3 columns) +3. Ops Templates +4. BDD/Gherkin +5. Gherkin File Organization (best practices) +6. Backend Tests (amar_django_back structure) +7. Frontend Tests (amar_frontend structure) +8. Per-Feature Checklist + +## Sources + +### Ops Templates +- `album/template/ops-flow/plantilla-flujo.md` +- `def/work_plan/21-plantilla-flujos-usuario.md` + +### BDD/Gherkin Examples +- `def/work_plan/10-flow-turnero.md` (full gherkin + tests example) + +### Test Structure References +- `amar_django_back/tests/contracts/README.md` +- `amar_frontend/tests/README.md` + +## Editing +Edit `index-en.html` or `index-es.html` directly. +Slides are `
` elements. Arrow keys to navigate. + +## Flow Checklist (per feature) + +- [ ] Ops template filled by support team +- [ ] Convert to .feature file (Gherkin spec) +- [ ] Backend: API contract tests per endpoint +- [ ] Backend: Workflow test (composition) +- [ ] Frontend: Page Object (if new page) +- [ ] Frontend: E2E spec (Playwright) +- [ ] Wire to CI diff --git a/atlas/book/feature-flow/index-en.html b/atlas/book/feature-flow/index-en.html new file mode 100644 index 0000000..9e10534 --- /dev/null +++ b/atlas/book/feature-flow/index-en.html @@ -0,0 +1,392 @@ + + + + + + Feature Flow - Standardization Pipeline + + + +
+

Feature Flow

+

Standardization Pipeline

+
Ops Templates → BDD/Gherkin → Backend + Frontend Tests
+
+ +
+

Pipeline Overview

+
+
+

1 Ops Templates

+
    +
  • Non-technical language
  • +
  • User perspective flows
  • +
  • From support/ops team
  • +
  • Captures edge cases
  • +
  • Documents known problems
  • +
+
+
+

2 BDD/Gherkin

+
    +
  • .feature files
  • +
  • Given/When/Then syntax
  • +
  • Human readable specs
  • +
  • Single source of truth
  • +
  • Maps to both test types
  • +
+
+
+

3 Tests

+
    +
  • Backend: API contracts
  • +
  • Backend: Workflows (compositions)
  • +
  • Frontend: Page Objects
  • +
  • Frontend: E2E specs (Playwright)
  • +
+
+
+
+ +
+

1 Ops Templates

+
+
+

Template Structure

+
+### [Flow Name]
+
+User type: Pet Owner / Vet / Admin
+Entry point: Page/button/link
+Goal: One sentence
+
+Steps:
+1. First action
+2. Second action
+3. ...
+
+Expected result:
+- What should happen
+
+Common problems:
+- Problem 1
+
+Edge cases:
+- Special case 1
+        
+
+
+

Source

+
    +
  • Template: album/template/ops-flow/
  • +
  • Reference: def/work_plan/21-plantilla
  • +
+

Who Fills This

+
    +
  • Support team (daily user contact)
  • +
  • Ops team (knows workarounds)
  • +
  • Product (requirements)
  • +
+

Output

+
    +
  • One .md per flow
  • +
  • Organized by user type
  • +
+
+
+
+ +
+

2 BDD/Gherkin

+
+
+

.feature File

+
+Feature: Turnero - Book appointment
+
+  Scenario: Book vaccination for cat
+    Given I am on the turnero page
+    When I enter address "Av Santa Fe 1234"
+    And I click "Next"
+    Then a guest user should be created
+
+    When I add pet "Koshka" type "Cat"
+    And I select "Vaccination"
+    Then "Clinical consult" is auto-added
+
+  Scenario: Services filtered by pet type
+    Given I added a cat
+    Then I see cat vaccines
+    And I dont see dog vaccines
+        
+
+
+

Keywords

+
    +
  • Feature = one capability
  • +
  • Scenario = one behavior
  • +
  • Given = precondition
  • +
  • When = action
  • +
  • Then = expected result
  • +
+

Reference

+
    +
  • def/work_plan/10-flow-turnero.md
  • +
  • Full example with Gherkin, API tests, Page Objects
  • +
+
+
+
+ +
+

2b Gherkin File Organization

+
+
+

Correct: One Feature = One File

+
+features/
+├── pet-owner/
+│   ├── registro.feature      # 6-8 scenarios
+│   ├── reservar-turno.feature # 10-15 scenarios
+│   ├── gestion-mascotas.feature
+│   └── pago.feature
+├── veterinarian/
+│   └── ...
+└── backoffice/
+    └── ...
+        
+

Anti-pattern: One Scenario = One File

+
+# DON'T do this
+features/pet-owner/registro/
+├── registro-exitoso.feature
+├── registro-email-invalido.feature
+├── registro-password-corto.feature
+└── ... (dozens of tiny files)
+        
+
+
+

Why Multiple Scenarios per File

+
    +
  • Feature = Capability - one file describes one capability with all its behaviors
  • +
  • Context stays together - Background, Rules share context
  • +
  • Tooling expects it - test runners, reports, IDE navigation
  • +
+

When to Split

+
+# Scenarios per file:
+ 5-20   Normal, keep as is
+20-40   Consider splitting
+40+     Definitely split
+        
+

Folder Depth

+
    +
  • Good: 1-2 levels max
  • +
  • Avoid: deep nesting
  • +
+
+
+
+ +
+

3a Backend Tests

+
+
+

Structure (amar_django_back)

+
+tests/contracts/
+├── base.py          # mode switcher
+├── endpoints.py     # API paths (single source)
+├── helpers.py       # test data
+│
+├── mascotas/        # app tests
+│   ├── test_pet_owners.py
+│   ├── test_pets.py
+│   └── test_coverage.py
+├── productos/
+│   ├── test_services.py
+│   └── test_cart.py
+├── solicitudes/
+│   └── test_service_requests.py
+│
+└── workflows/       # compositions
+    └── test_turnero_general.py
+        
+
+
+

Two Test Modes

+
+# Fast (Django test client)
+pytest tests/contracts/
+
+# Live (real HTTP)
+CONTRACT_TEST_MODE=live pytest
+        
+

Workflow = Composition

+
+# Calls endpoints in sequence:
+1. Check coverage
+2. Create pet owner
+3. Create pet
+4. Get services
+5. Create request
+        
+

Key Files

+
    +
  • endpoints.py - change paths here only
  • +
  • helpers.py - sample data
  • +
+
+
+
+ +
+

3b Frontend Tests

+
+
+

Structure (amar_frontend)

+
+tests/e2e/
+├── pages/           # Page Objects
+│   ├── BasePage.ts
+│   ├── LoginPage.ts
+│   └── index.ts
+│
+└── login.spec.ts   # E2E test
+        
+

Page Object Pattern

+
+export class LoginPage extends BasePage {
+  get emailInput() {
+    return this.page.getByLabel('Email');
+  }
+
+  async login(email, password) {
+    await this.emailInput.fill(email);
+    await this.passwordInput.fill(password);
+    await this.submitButton.click();
+  }
+}
+        
+
+
+

Running Tests

+
+# All tests
+npx playwright test
+
+# With UI
+npx playwright test --ui
+
+# Specific file
+npx playwright test login.spec.ts
+        
+

Locator Priority

+
    +
  • 1. getByRole() - buttons, links
  • +
  • 2. getByLabel() - form fields
  • +
  • 3. getByText() - visible text
  • +
  • 4. getByTestId() - data-testid
  • +
+

Avoid

+
    +
  • CSS class selectors
  • +
  • Complex XPath
  • +
+
+
+
+ +
+

Per-Feature Checklist

+
    +
  • Ops template filled (support team)
  • +
  • Convert to .feature file (Gherkin spec)
  • +
  • Backend: API contract tests per endpoint
  • +
  • Backend: Workflow test (composition)
  • +
  • Frontend: Page Object (if new page)
  • +
  • Frontend: E2E spec (Playwright)
  • +
  • Wire to CI
  • +
+
+

Full example: def/work_plan/10-flow-turnero.md

+

Backend README: amar_django_back/tests/contracts/README.md

+

Frontend README: amar_frontend/tests/README.md

+
+
+ + + + + + diff --git a/atlas/book/feature-flow/index-es.html b/atlas/book/feature-flow/index-es.html new file mode 100644 index 0000000..fbb31cb --- /dev/null +++ b/atlas/book/feature-flow/index-es.html @@ -0,0 +1,470 @@ + + + + + + Feature Flow - Pipeline de Estandarizacion + + + +
+

Feature Flow

+

Pipeline de Estandarizacion

+
Templates Ops → BDD/Gherkin → Tests Backend + Frontend
+
+ +
+

Vision General del Pipeline

+
+
+

1 Templates Ops

+
    +
  • Lenguaje no tecnico
  • +
  • Flujos desde el usuario
  • +
  • Del equipo de soporte/ops
  • +
  • Captura casos borde
  • +
  • Documenta problemas conocidos
  • +
+
+
+

2 BDD/Gherkin

+
    +
  • Archivos .feature
  • +
  • Sintaxis Given/When/Then
  • +
  • Specs legibles
  • +
  • Fuente unica de verdad
  • +
  • Mapea a ambos tipos de test
  • +
+
+
+

3 Tests

+
    +
  • Backend: Contratos API
  • +
  • Backend: Workflows (composiciones)
  • +
  • Frontend: Page Objects
  • +
  • Frontend: E2E specs (Playwright)
  • +
+
+
+
+ +
+

1 Templates Ops

+
+
+

Estructura de la Plantilla

+
+### [Nombre del Flujo]
+
+Tipo de usuario: Dueno / Vet / Admin
+Donde empieza: Pagina/boton/link
+Objetivo: Una oracion
+
+Pasos:
+1. Primera accion
+2. Segunda accion
+3. ...
+
+Que deberia pasar:
+- Resultado esperado
+
+Problemas comunes:
+- Problema 1
+
+Casos especiales:
+- Caso especial 1
+        
+
+
+

Fuente

+
    +
  • Plantilla: album/template/ops-flow/
  • +
  • Referencia: def/work_plan/21-plantilla
  • +
+

Quien Completa Esto

+
    +
  • Equipo de soporte (contacto diario)
  • +
  • Equipo de ops (conoce workarounds)
  • +
  • Producto (requerimientos)
  • +
+

Output

+
    +
  • Un .md por flujo
  • +
  • Organizado por tipo de usuario
  • +
+
+
+
+ +
+

2 BDD/Gherkin

+
+
+

Archivo .feature

+
+Feature: Turnero - Reservar turno
+
+  Scenario: Reservar vacuna para gato
+    Given estoy en la pagina del turnero
+    When ingreso direccion "Av Santa Fe 1234"
+    And hago click en "Siguiente"
+    Then se crea un usuario invitado
+
+    When agrego mascota "Koshka" tipo "Gato"
+    And selecciono "Vacunacion"
+    Then "Consulta clinica" se agrega auto
+
+  Scenario: Servicios filtrados por tipo
+    Given agregue un gato
+    Then veo vacunas felinas
+    And no veo vacunas caninas
+        
+
+
+

Palabras Clave

+
    +
  • Feature = una funcionalidad
  • +
  • Scenario = un comportamiento
  • +
  • Given = precondicion
  • +
  • When = accion
  • +
  • Then = resultado esperado
  • +
+

Referencia

+
    +
  • def/work_plan/10-flow-turnero.md
  • +
  • Ejemplo completo con Gherkin, tests API, Page Objects
  • +
+
+
+
+ +
+

2b Organizacion de Archivos Gherkin

+
+
+

Correcto: Una Feature = Un Archivo

+
+features/
+├── pet-owner/
+│   ├── registro.feature      # 6-8 escenarios
+│   ├── reservar-turno.feature # 10-15 escenarios
+│   ├── gestion-mascotas.feature
+│   └── pago.feature
+├── veterinarian/
+│   └── ...
+└── backoffice/
+    └── ...
+        
+

Anti-patron: Un Escenario = Un Archivo

+
+# NO hacer esto
+features/pet-owner/registro/
+├── registro-exitoso.feature
+├── registro-email-invalido.feature
+├── registro-password-corto.feature
+└── ... (docenas de archivos pequeños)
+        
+
+
+

Por Que Multiples Escenarios por Archivo

+
    +
  • Feature = Capacidad - un archivo describe una capacidad con todos sus comportamientos
  • +
  • Contexto junto - Background, Rules comparten contexto
  • +
  • Tooling lo espera - test runners, reportes, navegacion IDE
  • +
+

Cuando Dividir

+
+# Escenarios por archivo:
+ 5-20   Normal, mantener
+20-40   Considerar dividir
+40+     Definitivamente dividir
+        
+

Profundidad de Carpetas

+
    +
  • Bien: 1-2 niveles max
  • +
  • Evitar: anidamiento profundo
  • +
+
+
+
+ +
+

3a Tests Backend

+
+
+

Estructura (amar_django_back)

+
+tests/contracts/
+├── base.py          # switcher de modo
+├── endpoints.py     # paths API (fuente unica)
+├── helpers.py       # datos de prueba
+│
+├── mascotas/        # tests por app
+│   ├── test_pet_owners.py
+│   ├── test_pets.py
+│   └── test_coverage.py
+├── productos/
+│   ├── test_services.py
+│   └── test_cart.py
+├── solicitudes/
+│   └── test_service_requests.py
+│
+└── workflows/       # composiciones
+    └── test_turnero_general.py
+        
+
+
+

Dos Modos de Test

+
+# Rapido (Django test client)
+pytest tests/contracts/
+
+# Live (HTTP real)
+CONTRACT_TEST_MODE=live pytest
+        
+

Workflow = Composicion

+
+# Llama endpoints en secuencia:
+1. Check cobertura
+2. Crear pet owner
+3. Crear mascota
+4. Obtener servicios
+5. Crear solicitud
+        
+

Archivos Clave

+
    +
  • endpoints.py - cambiar paths solo aca
  • +
  • helpers.py - datos de ejemplo
  • +
+
+
+
+ +
+

3b Tests Frontend

+
+
+

Estructura (amar_frontend)

+
+tests/e2e/
+├── pages/           # Page Objects
+│   ├── BasePage.ts
+│   ├── LoginPage.ts
+│   └── index.ts
+│
+└── login.spec.ts   # test E2E
+        
+

Patron Page Object

+
+export class LoginPage extends BasePage {
+  get emailInput() {
+    return this.page.getByLabel('Email');
+  }
+
+  async login(email, password) {
+    await this.emailInput.fill(email);
+    await this.passwordInput.fill(password);
+    await this.submitButton.click();
+  }
+}
+        
+
+
+

Ejecutar Tests

+
+# Todos los tests
+npx playwright test
+
+# Con UI
+npx playwright test --ui
+
+# Archivo especifico
+npx playwright test login.spec.ts
+        
+

Prioridad de Locators

+
    +
  • 1. getByRole() - botones, links
  • +
  • 2. getByLabel() - campos de form
  • +
  • 3. getByText() - texto visible
  • +
  • 4. getByTestId() - data-testid
  • +
+

Evitar

+
    +
  • Selectores de clases CSS
  • +
  • XPath complejos
  • +
+
+
+
+ +
+

Checklist por Feature

+
    +
  • Template ops completado (equipo soporte)
  • +
  • Convertir a archivo .feature (spec Gherkin)
  • +
  • Backend: Tests de contrato API por endpoint
  • +
  • Backend: Test workflow (composicion)
  • +
  • Frontend: Page Object (si es pagina nueva)
  • +
  • Frontend: E2E spec (Playwright)
  • +
  • Conectar a CI
  • +
+
+

Ejemplo completo: def/work_plan/10-flow-turnero.md

+

README Backend: amar_django_back/tests/contracts/README.md

+

README Frontend: amar_frontend/tests/README.md

+
+
+ + + + + + diff --git a/atlas/book/feature-form-samples/CLAUDE.md b/atlas/book/feature-form-samples/CLAUDE.md new file mode 100644 index 0000000..e02eb18 --- /dev/null +++ b/atlas/book/feature-form-samples/CLAUDE.md @@ -0,0 +1,79 @@ +# Ops Templates Sample Book + +## Purpose +**SAMPLE DATA** - Example filled ops/support templates for demonstration and testing purposes. These are realistic examples to work with while actual definitions are pending from ops/support team. + +Use these to: +- Understand the template structure +- Test tooling and workflows +- Demo the feature-flow pipeline + +Real templates will follow the same structure but with validated content. + +## Structure + +``` +ops-templates-sample/ +├── pet-owner/ # 5 flows for pet owners +│ ├── 01-registro.md +│ ├── 02-reservar-turno.md +│ ├── 03-gestion-mascotas.md +│ ├── 04-pago-turno.md +│ └── 05-historial-medico.md +├── veterinarian/ # 5 flows for vets +│ ├── 01-aceptar-solicitud.md +│ ├── 02-gestion-agenda.md +│ ├── 03-realizar-visita.md +│ ├── 04-zonas-cobertura.md +│ └── 05-historial-pacientes.md +└── backoffice/ # 5 flows for admins + ├── 01-gestion-solicitudes.md + ├── 02-gestion-usuarios.md + ├── 03-gestion-servicios.md + ├── 04-reembolsos.md + └── 05-reportes.md +``` + +## Template Format + +Each file follows the standard ops template: + +```markdown +# [Nombre del Flujo] + +## Tipo de usuario +## Donde empieza +## Que quiere hacer el usuario +## Pasos +## Que deberia pasar +## Problemas comunes +## Casos especiales +## Flujos relacionados +## Notas tecnicas +``` + +## Corresponding Gherkin + +Each template has corresponding `.feature` files in: +- `album/book/gherkin-sample/es/` - Spanish keywords (Dado/Cuando/Entonces) +- `album/book/gherkin-sample/en/` - English keywords (Given/When/Then) + +## Usage + +1. **Support/Ops**: Use templates to document new flows +2. **Dev**: Convert templates to Gherkin specs +3. **QA**: Use Gherkin to derive test cases +4. **Product**: Review templates for completeness + +## Source Template + +The base template is at: `album/template/ops-flow/plantilla-flujo.md` + +## Coverage + +| Area | Flows | Status | +|------|-------|--------| +| Pet Owner | 5 | Complete | +| Veterinarian | 5 | Complete | +| Backoffice | 5 | Complete | +| **Total** | **15** | | diff --git a/atlas/book/feature-form-samples/detail.html b/atlas/book/feature-form-samples/detail.html new file mode 100644 index 0000000..0732da2 --- /dev/null +++ b/atlas/book/feature-form-samples/detail.html @@ -0,0 +1,335 @@ + + + + + + {{ filename }} - Ops Template + + + +
+
+ +

{{ filename.replace('.md', '').replace('-', ' ').title() }}

+
+ {{ user_type }} + Sample +
+
+ +
+
+

User Flow Template

+
+
+ +
+
+ + + + +
+ + + + diff --git a/atlas/book/feature-form-samples/feature-form/.larder b/atlas/book/feature-form-samples/feature-form/.larder new file mode 100644 index 0000000..e69de29 diff --git a/atlas/book/feature-form-samples/feature-form/backoffice/01-gestion-solicitudes.md b/atlas/book/feature-form-samples/feature-form/backoffice/01-gestion-solicitudes.md new file mode 100644 index 0000000..13661aa --- /dev/null +++ b/atlas/book/feature-form-samples/feature-form/backoffice/01-gestion-solicitudes.md @@ -0,0 +1,92 @@ +# Gestion de Solicitudes de Servicio + +## Tipo de usuario +Administrador / Operaciones + +## Donde empieza +Backoffice `/admin/` -> Dashboard o Seccion "Solicitudes" + +## Que quiere hacer el usuario +Ver, filtrar y gestionar todas las solicitudes de servicio del sistema + +## Pasos - Ver solicitudes + +1. Acceder al backoffice +2. Ir a seccion "Solicitudes" +3. Ver listado con todas las solicitudes +4. Usar filtros para encontrar solicitudes especificas: + - Por estado (Pendiente, Coordinado, Pagado, Completado, Cancelado) + - Por fecha + - Por barrio/zona + - Por veterinario + - Por dueno + +## Pasos - Ver detalle de solicitud + +1. Click en una solicitud del listado +2. Ver toda la informacion: + - Datos del dueno + - Datos de la mascota + - Servicios solicitados + - Fechas preferidas + - Veterinario asignado (si hay) + - Estado de pago + - Historial de cambios de estado + +## Pasos - Asignar veterinario manualmente + +1. Abrir solicitud en estado "Pendiente" +2. Click en "Asignar veterinario" +3. Ver lista de vets disponibles en la zona +4. Seleccionar veterinario +5. Seleccionar fecha y hora +6. Confirmar asignacion +7. Sistema notifica al vet y al dueno + +## Pasos - Cambiar estado manualmente + +1. Abrir solicitud +2. Click en "Cambiar estado" +3. Seleccionar nuevo estado +4. Ingresar motivo (si aplica) +5. Confirmar +6. Se registra en el historial + +## Que deberia pasar + +- Vision completa de todas las solicitudes +- Capacidad de intervenir cuando algo falla +- Trazabilidad de cambios +- Notificaciones a las partes afectadas + +## Problemas comunes + +- Solicitud sin vet disponible en la zona +- Usuario pago pero webhook fallo +- Vet no puede asistir y hay que reasignar +- Dueno quiere cancelar pero ya pago +- Solicitudes duplicadas del mismo dueno + +## Casos especiales + +- Solicitud urgente sin vets disponibles +- Reasignacion a ultimo momento +- Cancelacion con reembolso +- Solicitud con datos incompletos +- Dueno no responde a confirmaciones + +## Flujos relacionados + +- Proceso de reembolso +- Gestion de usuarios +- Reportes + +## Notas tecnicas + +- APIs: + - `GET /solicitudes/api/v1/service-requests/` (listado con filtros) + - `GET /solicitudes/api/v1/service-requests/{id}/` (detalle) + - `PATCH /solicitudes/api/v1/service-requests/{id}/` (asignar vet) + - `PATCH /solicitudes/api/v1/change-service-request-state/` (cambiar estado) +- Estados validos: pending, coordinated, payed, completed, cancelled +- Permisos: solo staff puede ver todas las solicitudes diff --git a/atlas/book/feature-form-samples/feature-form/backoffice/02-gestion-usuarios.md b/atlas/book/feature-form-samples/feature-form/backoffice/02-gestion-usuarios.md new file mode 100644 index 0000000..db75f6f --- /dev/null +++ b/atlas/book/feature-form-samples/feature-form/backoffice/02-gestion-usuarios.md @@ -0,0 +1,106 @@ +# Gestion de Usuarios + +## Tipo de usuario +Administrador / Operaciones + +## Donde empieza +Backoffice `/admin/` -> Seccion "Usuarios" o "Duenos" / "Veterinarios" + +## Que quiere hacer el usuario +Administrar duenos de mascotas y veterinarios del sistema + +## Pasos - Ver/Buscar duenos + +1. Ir a "Duenos de mascotas" +2. Ver listado de todos los duenos +3. Usar buscador (nombre, email, telefono) +4. Filtrar por: + - Tipo (Registrado / Invitado) + - Fecha de registro + - Con/sin turnos activos + +## Pasos - Ver detalle de dueno + +1. Click en un dueno +2. Ver informacion completa: + - Datos personales + - Direcciones registradas + - Mascotas + - Historial de solicitudes + - Historial de pagos + +## Pasos - Editar dueno + +1. En el detalle, click "Editar" +2. Modificar campos permitidos: + - Nombre + - Telefono + - Email + - Direccion +3. Guardar cambios + +## Pasos - Gestionar veterinarios + +1. Ir a "Veterinarios" +2. Ver listado con: + - Nombre y matricula + - Zonas de cobertura + - Estado (Activo/Inactivo) + - Turnos del mes + +## Pasos - Agregar nuevo veterinario + +1. Click en "Agregar veterinario" +2. Completar datos: + - Nombre completo + - Numero de matricula + - Email + - Telefono + - Especialidades + - Zonas de cobertura inicial +3. Crear credenciales de acceso +4. Guardar + +## Pasos - Desactivar veterinario + +1. Abrir perfil del vet +2. Click en "Desactivar" +3. Confirmar +4. Vet deja de recibir solicitudes +5. Visitas ya aceptadas se mantienen o se reasignan + +## Que deberia pasar + +- Gestion completa de usuarios del sistema +- Capacidad de corregir datos erroneos +- Alta/baja de veterinarios +- Vision del estado de cada usuario + +## Problemas comunes + +- Duenos duplicados (se registraron con email diferente) +- Vet quiere darse de baja pero tiene citas pendientes +- Email incorrecto y no reciben notificaciones +- Cuenta de invitado que quiere convertirse en registrada + +## Casos especiales + +- Fusionar cuentas duplicadas +- Transferir mascota a otro dueno +- Vet de vacaciones temporalmente +- Cambio de numero de matricula + +## Flujos relacionados + +- Gestion de solicitudes (ver historial por usuario) +- Reportes (filtrar por usuario) + +## Notas tecnicas + +- APIs: + - `GET /mascotas/api/v1/pet-owners/` (duenos) + - `GET /mascotas/api/v1/veterinarians/` (vets) + - `POST /mascotas/api/v1/veterinarians/` (crear vet) + - `PATCH /mascotas/api/v1/veterinarians/{id}/` (editar/desactivar) +- Permisos: solo staff puede modificar usuarios +- Soft delete para mantener historial diff --git a/atlas/book/feature-form-samples/feature-form/backoffice/03-gestion-servicios.md b/atlas/book/feature-form-samples/feature-form/backoffice/03-gestion-servicios.md new file mode 100644 index 0000000..38a759b --- /dev/null +++ b/atlas/book/feature-form-samples/feature-form/backoffice/03-gestion-servicios.md @@ -0,0 +1,98 @@ +# Gestion de Servicios y Precios + +## Tipo de usuario +Administrador + +## Donde empieza +Backoffice `/admin/` -> Seccion "Servicios" o Django Admin -> Productos + +## Que quiere hacer el usuario +Administrar el catalogo de servicios veterinarios y sus precios + +## Pasos - Ver servicios + +1. Ir a "Servicios" +2. Ver listado de todos los servicios +3. Filtrar por: + - Categoria (Vacunacion, Consulta, Cirugia, etc.) + - Tipo de mascota (Perro, Gato, Ambos) + - Estado (Activo/Inactivo) + +## Pasos - Agregar nuevo servicio + +1. Click en "Agregar servicio" +2. Completar datos: + - Nombre del servicio + - Descripcion + - Categoria + - Tipo de mascota aplicable + - Precio base + - Duracion estimada + - Requiere otros servicios (dependencias) +3. Guardar + +## Pasos - Editar servicio + +1. Abrir servicio existente +2. Modificar campos +3. Guardar +4. Cambios aplican a nuevas solicitudes + +## Pasos - Gestionar precios + +1. Abrir servicio +2. Ir a seccion "Precios" +3. Ver precio actual +4. Para cambiar precio: + - Click "Agregar precio" + - Ingresar monto + - Definir fecha de vigencia + - Guardar +5. Precio anterior queda en historial + +## Pasos - Configurar combos + +1. Ir a "Combos" o "Paquetes" +2. Crear nuevo combo: + - Nombre del combo + - Servicios incluidos + - Precio del combo (con descuento) + - Condiciones de aplicacion +3. Guardar + +## Que deberia pasar + +- Catalogo actualizado de servicios +- Precios correctos en el turnero +- Combos aplicados automaticamente +- Historial de precios para facturacion + +## Problemas comunes + +- Cambio de precio afecta solicitudes ya creadas +- Servicio deprecado pero con solicitudes pendientes +- Combo no se aplica automaticamente +- Precio diferente por zona/vet + +## Casos especiales + +- Servicio con precio variable (depende de peso mascota) +- Servicio estacional (solo ciertas epocas) +- Promocion temporal +- Precio especial por vet + +## Flujos relacionados + +- Turnero (muestra servicios disponibles) +- Facturacion (usa precios) + +## Notas tecnicas + +- APIs: + - `GET /productos/api/v1/services/` (servicios) + - `POST /productos/api/v1/services/` (crear) + - `PATCH /productos/api/v1/services/{id}/` (editar) + - `GET /productos/api/v1/prices/` (precios) + - `POST /productos/api/v1/prices/` (nuevo precio) +- Modelo Price tiene fecha de vigencia +- Precio se congela al momento de crear solicitud diff --git a/atlas/book/feature-form-samples/feature-form/backoffice/04-reembolsos.md b/atlas/book/feature-form-samples/feature-form/backoffice/04-reembolsos.md new file mode 100644 index 0000000..877ce8e --- /dev/null +++ b/atlas/book/feature-form-samples/feature-form/backoffice/04-reembolsos.md @@ -0,0 +1,82 @@ +# Proceso de Reembolso + +## Tipo de usuario +Administrador / Operaciones + +## Donde empieza +Backoffice -> Solicitud pagada -> "Procesar reembolso" o Seccion "Pagos" + +## Que quiere hacer el usuario +Procesar un reembolso para un turno que fue cancelado despues del pago + +## Pasos - Identificar solicitud a reembolsar + +1. Buscar la solicitud por: + - ID de solicitud + - Nombre del dueno + - Numero de operacion de Mercado Pago +2. Verificar que esta en estado "Pagado" +3. Verificar que el pago fue exitoso + +## Pasos - Procesar reembolso + +1. Abrir detalle de la solicitud +2. Click en "Procesar reembolso" +3. Ver informacion del pago: + - Monto pagado + - Fecha de pago + - Metodo de pago + - ID de operacion MP +4. Seleccionar tipo de reembolso: + - Total + - Parcial (ingresar monto) +5. Ingresar motivo del reembolso +6. Confirmar +7. Sistema procesa reembolso en Mercado Pago +8. Actualizar estado de la solicitud + +## Pasos - Verificar reembolso + +1. Ir a seccion "Pagos" o historial de la solicitud +2. Verificar que el reembolso fue procesado +3. Ver estado del reembolso en Mercado Pago + +## Que deberia pasar + +- Reembolso procesado en Mercado Pago +- Dueno recibe el dinero (segun tiempos de MP) +- Solicitud actualiza estado +- Registro en el historial +- Dueno notificado por email + +## Problemas comunes + +- Reembolso parcial no soportado por el metodo de pago +- Plazo de reembolso vencido (politica de MP) +- Dueno reclama que no recibio el dinero +- Reembolso duplicado por error +- Pago original en disputa + +## Casos especiales + +- Reembolso por mala atencion +- Reembolso parcial (solo algunos servicios) +- Visita incompleta (reembolso proporcional) +- Dueno quiere credito en vez de reembolso +- Reembolso fuera de plazo (solucion alternativa) + +## Flujos relacionados + +- Gestion de solicitudes +- Cancelacion de turno +- Atencion al cliente + +## Notas tecnicas + +- API Mercado Pago: `POST /v1/payments/{id}/refunds` +- Tiempos de acreditacion: + - Tarjeta credito: 1-2 estados de cuenta + - Tarjeta debito: 5-10 dias habiles + - Dinero en cuenta MP: inmediato +- Plazo maximo para reembolso: 180 dias desde el pago +- Se debe guardar log de todos los reembolsos diff --git a/atlas/book/feature-form-samples/feature-form/backoffice/05-reportes.md b/atlas/book/feature-form-samples/feature-form/backoffice/05-reportes.md new file mode 100644 index 0000000..05bba99 --- /dev/null +++ b/atlas/book/feature-form-samples/feature-form/backoffice/05-reportes.md @@ -0,0 +1,99 @@ +# Reportes y Dashboard + +## Tipo de usuario +Administrador / Gerencia + +## Donde empieza +Backoffice `/admin/` -> Dashboard o Seccion "Reportes" + +## Que quiere hacer el usuario +Ver metricas del negocio, generar reportes y analizar datos + +## Pasos - Ver dashboard + +1. Acceder al backoffice +2. Ver dashboard principal con: + - Solicitudes del dia/semana/mes + - Ingresos del periodo + - Visitas completadas + - Conversion (solicitudes -> pagos) + - Vets activos + +## Pasos - Generar reporte de solicitudes + +1. Ir a "Reportes" -> "Solicitudes" +2. Seleccionar rango de fechas +3. Filtrar por: + - Estado + - Zona + - Veterinario + - Tipo de servicio +4. Click "Generar" +5. Ver tabla con resultados +6. Opcion de exportar (CSV/Excel) + +## Pasos - Generar reporte de ingresos + +1. Ir a "Reportes" -> "Ingresos" +2. Seleccionar periodo +3. Agrupar por: + - Dia/Semana/Mes + - Veterinario + - Servicio + - Zona +4. Ver grafico y tabla +5. Exportar si es necesario + +## Pasos - Reporte de veterinarios + +1. Ir a "Reportes" -> "Veterinarios" +2. Ver para cada vet: + - Visitas completadas + - Calificacion promedio + - Ingresos generados + - Tasa de cancelacion +3. Filtrar por periodo +4. Ordenar por metrica + +## Pasos - Exportar datos + +1. En cualquier reporte, click "Exportar" +2. Seleccionar formato (CSV, Excel) +3. Descargar archivo +4. Usar para analisis externo o contabilidad + +## Que deberia pasar + +- Vision clara del estado del negocio +- Datos para toma de decisiones +- Reportes exportables para contabilidad +- Identificacion de tendencias + +## Problemas comunes + +- Datos no actualizados en tiempo real +- Filtros que no devuelven lo esperado +- Discrepancia entre reportes y realidad +- Exportacion con formato incorrecto + +## Casos especiales + +- Reporte de prueba vs produccion +- Datos historicos antes de implementar filtro +- Comparativa año vs año +- Reporte personalizado para inversores + +## Flujos relacionados + +- Gestion de solicitudes (fuente de datos) +- Facturacion AFIP +- Google Sheets sync + +## Notas tecnicas + +- API: + - `GET /mascotas/api/v1/stats-summary` (metricas principales) + - `GET /solicitudes/api/v1/service-requests/?date_from=X&date_to=Y` (datos crudos) +- Google Sheets: sincronizacion automatica via Celery +- Dashboard actualiza cada 5 minutos +- Reportes pesados: generar async y notificar cuando estan listos diff --git a/atlas/book/feature-form-samples/feature-form/pet-owner/01-registro.md b/atlas/book/feature-form-samples/feature-form/pet-owner/01-registro.md new file mode 100644 index 0000000..9948c3f --- /dev/null +++ b/atlas/book/feature-form-samples/feature-form/pet-owner/01-registro.md @@ -0,0 +1,55 @@ +# Registro de Usuario + +## Tipo de usuario +Dueno de mascota (nuevo) + +## Donde empieza +Pagina principal -> Boton "Registrarse" o `/register` + +## Que quiere hacer el usuario +Crear una cuenta nueva para poder reservar turnos y gestionar sus mascotas + +## Pasos + +1. Click en "Registrarse" en la pagina principal +2. Ingresar email +3. Ingresar contraseña +4. Confirmar contraseña +5. Aceptar terminos y condiciones +6. Click en "Crear cuenta" +7. Recibir email de verificacion +8. Click en el link de verificacion +9. Cuenta activada, redirige al dashboard + +## Que deberia pasar + +- Usuario creado en el sistema +- Email de bienvenida enviado +- Puede iniciar sesion inmediatamente +- Ve su dashboard vacio (sin mascotas, sin turnos) + +## Problemas comunes + +- El email ya esta registrado pero el usuario no lo recuerda +- El email de verificacion llega a spam +- La contraseña no cumple los requisitos minimos +- El usuario cierra el browser antes de verificar y no encuentra el email despues +- Usuarios intentan registrarse con email de otra persona + +## Casos especiales + +- Si el email existe como usuario invitado (del turnero), deberia linkear las cuentas +- Si el usuario ya tiene cuenta pero no verificada, reenviar email de verificacion +- Registro desde el flujo de turnero (ya ingreso datos, solo falta contraseña) + +## Flujos relacionados + +- Login (despues de registrarse) +- Turnero (puede disparar registro) +- Recuperar contraseña (si ya tenia cuenta y no recuerda) + +## Notas tecnicas + +- API: `POST /common/users/create/` +- Validacion de contraseña: minimo 8 caracteres, 1 numero, 1 mayuscula +- Verificacion expira en 24 horas diff --git a/atlas/book/feature-form-samples/feature-form/pet-owner/02-reservar-turno.md b/atlas/book/feature-form-samples/feature-form/pet-owner/02-reservar-turno.md new file mode 100644 index 0000000..f882aad --- /dev/null +++ b/atlas/book/feature-form-samples/feature-form/pet-owner/02-reservar-turno.md @@ -0,0 +1,77 @@ +# Reservar Turno (Turnero) + +## Tipo de usuario +Dueno de mascota (invitado, registrado, o recurrente) + +## Donde empieza +Pagina principal -> Boton "Agendar visita" o enlace directo `/turnero` + +## Que quiere hacer el usuario +Reservar un turno veterinario a domicilio para su mascota + +## Pasos + +1. Click en "Agendar visita" +2. Ingresar direccion en el popup de cobertura +3. Sistema verifica si hay cobertura en esa zona +4. Si hay cobertura, click "Siguiente" +5. Completar datos de la mascota: + - Nombre + - Tipo (Perro/Gato) + - Raza (opcional) + - Edad aproximada + - Peso aproximado + - Si esta castrado/a +6. Seleccionar categoria de servicio (Vacunacion, Consulta, etc.) +7. Seleccionar servicios especificos +8. Ver que algunos servicios se agregan automaticamente (ej: consulta clinica) +9. Seleccionar fechas preferidas (hasta 3) +10. Seleccionar franja horaria preferida +11. Ingresar datos de contacto: + - Nombre completo + - Telefono + - Email +12. Revisar resumen +13. Enviar solicitud + +## Que deberia pasar + +- Solicitud creada con estado "Pendiente" +- Email de confirmacion al usuario +- Notificacion a veterinarios de la zona +- Aparece en "Mis turnos" del usuario + +## Problemas comunes + +- No hay cobertura en la zona del usuario +- El autocompletado de direcciones no encuentra la calle +- Usuario no puede quitar "Consulta clinica" cuando selecciona vacunacion +- Confunden "fecha preferida" con "fecha confirmada" +- No entienden por que no pueden elegir hora exacta +- El total no se muestra hasta el final y sorprende al usuario + +## Casos especiales + +- Usuario invitado: se crea como guest, puede registrarse despues +- Usuario logueado: datos pre-llenados, mascota puede ser existente +- Usuario recurrente (mismo email): sistema detecta y sugiere linkear +- Mascota ya castrada: no mostrar servicio de castracion +- Servicios combo: algunos incluyen otros automaticamente +- Zona sin cobertura: mostrar formulario para avisar cuando haya + +## Flujos relacionados + +- Ver mis turnos (despues de reservar) +- Pagar turno (cuando se coordina) +- Cancelar turno +- Agregar mascota (si ya tiene cuenta) + +## Notas tecnicas + +- APIs: + - `POST /mascotas/api/v1/coverage-check/` (verificar cobertura) + - `POST /mascotas/api/v1/pet-owners/` (crear dueno invitado) + - `POST /mascotas/api/v1/pets/` (crear mascota) + - `GET /productos/api/v1/services/` (obtener servicios) + - `POST /solicitudes/api/v1/service-requests/` (crear solicitud) +- Estados de solicitud: pending -> coordinated -> payed -> completed diff --git a/atlas/book/feature-form-samples/feature-form/pet-owner/03-gestion-mascotas.md b/atlas/book/feature-form-samples/feature-form/pet-owner/03-gestion-mascotas.md new file mode 100644 index 0000000..4e92310 --- /dev/null +++ b/atlas/book/feature-form-samples/feature-form/pet-owner/03-gestion-mascotas.md @@ -0,0 +1,79 @@ +# Gestion de Mascotas + +## Tipo de usuario +Dueno de mascota (registrado) + +## Donde empieza +Dashboard -> Seccion "Mis mascotas" o `/user/pets` + +## Que quiere hacer el usuario +Agregar, editar o ver informacion de sus mascotas + +## Pasos - Agregar mascota + +1. Ir a "Mis mascotas" +2. Click en "Agregar mascota" +3. Completar formulario: + - Nombre (obligatorio) + - Tipo: Perro o Gato (obligatorio) + - Raza (opcional, lista segun tipo) + - Fecha de nacimiento o edad aproximada + - Peso en kg (opcional) + - Sexo + - Esta castrado/a (checkbox) + - Foto (opcional) +4. Guardar + +## Pasos - Editar mascota + +1. Ir a "Mis mascotas" +2. Click en la tarjeta de la mascota +3. Click en "Editar" +4. Modificar campos deseados +5. Guardar + +## Pasos - Ver historial medico + +1. Ir a "Mis mascotas" +2. Click en la tarjeta de la mascota +3. Ver seccion "Historial de visitas" +4. Click en una visita para ver el informe completo + +## Que deberia pasar + +- Mascota aparece en el listado +- Disponible para seleccionar en el turnero +- Historial medico accesible +- Foto visible en la tarjeta + +## Problemas comunes + +- Usuarios quieren agregar mascotas de otros tipos (conejo, ave) - no soportado +- No encuentran donde ver el historial medico +- Quieren eliminar mascota pero tiene turnos pendientes +- Foto muy grande no sube +- Confunden edad con fecha de nacimiento + +## Casos especiales + +- Mascota creada desde turnero como invitado: aparece cuando linkea cuenta +- Mascota fallecida: deberia poder marcarse (soft delete) +- Multiples mascotas con mismo nombre: permitido pero confuso +- Cambio de peso significativo: registrar historial? + +## Flujos relacionados + +- Reservar turno (seleccionar mascota existente) +- Ver historial medico +- Registro (crear primera mascota) + +## Notas tecnicas + +- APIs: + - `GET /mascotas/api/v1/pets/` (listar) + - `POST /mascotas/api/v1/pets/` (crear) + - `PATCH /mascotas/api/v1/pets/{id}/` (editar) + - `DELETE /mascotas/api/v1/pets/{id}/` (soft delete) + - `GET /mascotas/api/v1/vet-visits/?pet={id}` (historial) +- Tipos soportados: solo Perro y Gato por ahora +- Razas: lista predefinida por tipo diff --git a/atlas/book/feature-form-samples/feature-form/pet-owner/04-pago-turno.md b/atlas/book/feature-form-samples/feature-form/pet-owner/04-pago-turno.md new file mode 100644 index 0000000..d0a2d44 --- /dev/null +++ b/atlas/book/feature-form-samples/feature-form/pet-owner/04-pago-turno.md @@ -0,0 +1,66 @@ +# Pago de Turno + +## Tipo de usuario +Dueno de mascota (con turno coordinado) + +## Donde empieza +Email de confirmacion de turno -> Link "Pagar" o Dashboard -> Mis turnos -> Boton "Pagar" + +## Que quiere hacer el usuario +Pagar un turno que ya fue coordinado con fecha y veterinario asignado + +## Pasos + +1. Recibir email/notificacion de que el turno fue coordinado +2. Ver detalle del turno con fecha, hora y veterinario asignado +3. Verificar el monto total +4. Click en "Pagar" +5. Redireccion a Mercado Pago +6. Seleccionar metodo de pago: + - Tarjeta de credito/debito + - Dinero en cuenta MP + - Transferencia bancaria + - Efectivo (Rapipago/PagoFacil) +7. Completar pago +8. Redireccion de vuelta a la plataforma +9. Ver confirmacion de pago exitoso + +## Que deberia pasar + +- Estado del turno cambia a "Pagado" +- Email de confirmacion de pago +- Comprobante disponible +- Veterinario notificado +- Turno aparece como confirmado en el calendario + +## Problemas comunes + +- Usuario cierra el browser durante el pago y no sabe si se proceso +- Pago rechazado por fondos insuficientes +- Link de pago expira y no pueden pagr +- Quieren pagar en efectivo el dia de la visita +- Precio cambio entre coordinacion y pago +- Quieren aplicar descuento/cupon y no encuentran donde + +## Casos especiales + +- Pago parcial: no soportado actualmente +- Pago con tarjeta rechazada: puede reintentar +- Pago por transferencia: demora en acreditarse (pendiente de confirmacion) +- Usuario paga pero webhook falla: requiere intervencion manual +- Turno cancelado despues de pagar: proceso de reembolso + +## Flujos relacionados + +- Ver mis turnos (antes y despues de pagar) +- Cancelar turno (si decide no pagar) +- Contactar soporte (problemas con pago) + +## Notas tecnicas + +- APIs: + - `POST /payments/api/v1/requests/{id}/preference/` (generar link MP) + - Webhook: `POST /payments/mp/webhook/` (notificacion de MP) +- Integracion: Mercado Pago Checkout Pro +- Estados: coordinated -> payed (despues de webhook exitoso) +- Timeout de preferencia: 24 horas diff --git a/atlas/book/feature-form-samples/feature-form/pet-owner/05-historial-medico.md b/atlas/book/feature-form-samples/feature-form/pet-owner/05-historial-medico.md new file mode 100644 index 0000000..481cf0d --- /dev/null +++ b/atlas/book/feature-form-samples/feature-form/pet-owner/05-historial-medico.md @@ -0,0 +1,79 @@ +# Ver Historial Medico + +## Tipo de usuario +Dueno de mascota (registrado, con visitas completadas) + +## Donde empieza +Dashboard -> Mis mascotas -> Mascota -> "Ver historial" o Mis turnos -> Turno completado -> "Ver informe" + +## Que quiere hacer el usuario +Ver los informes medicos de las visitas veterinarias de su mascota + +## Pasos - Desde mascotas + +1. Ir a "Mis mascotas" +2. Seleccionar la mascota +3. Ver seccion "Historial de visitas" +4. Lista de visitas ordenadas por fecha (mas reciente primero) +5. Click en una visita para ver detalle + +## Pasos - Desde turnos + +1. Ir a "Mis turnos" +2. Filtrar por "Completados" +3. Click en un turno completado +4. Click en "Ver informe medico" + +## Que muestra el informe + +- Fecha de la visita +- Veterinario que atendio +- Motivo de consulta / servicios realizados +- Examen fisico: + - Peso registrado + - Temperatura + - Frecuencia cardiaca + - Observaciones generales +- Diagnostico +- Tratamiento indicado +- Medicamentos recetados (nombre, dosis, frecuencia, duracion) +- Estudios solicitados +- Proximos pasos / seguimiento recomendado +- Observaciones adicionales + +## Que deberia pasar + +- Usuario puede ver todos los informes de sus mascotas +- Puede descargar/imprimir el informe +- Informacion clara y entendible + +## Problemas comunes + +- Visita completada pero informe no cargado todavia +- Usuario quiere editar algo del informe (no puede, es del vet) +- Terminologia medica confusa +- No encuentra informe de visita antigua +- Quiere compartir informe con otro veterinario + +## Casos especiales + +- Visita sin informe: vet no lo cargo (deberia ser obligatorio) +- Informe incompleto: campos vacios +- Multiples visitas mismo dia: mostrar todas +- Mascota transferida de otro dueno: historial previo? +- Estudios pendientes: mostrar estado + +## Flujos relacionados + +- Gestion de mascotas +- Reservar turno de seguimiento +- Contactar veterinario (dudas sobre informe) + +## Notas tecnicas + +- APIs: + - `GET /mascotas/api/v1/vet-visits/?pet={id}` (listar visitas) + - `GET /mascotas/api/v1/vet-visits/{id}/` (detalle visita) + - `GET /mascotas/api/v1/vetvisitreport/?vet_visit={id}` (informe) +- PDF: generado on-demand o pre-generado? +- Permisos: solo dueno de la mascota puede ver diff --git a/atlas/book/feature-form-samples/feature-form/veterinarian/01-aceptar-solicitud.md b/atlas/book/feature-form-samples/feature-form/veterinarian/01-aceptar-solicitud.md new file mode 100644 index 0000000..bdf93be --- /dev/null +++ b/atlas/book/feature-form-samples/feature-form/veterinarian/01-aceptar-solicitud.md @@ -0,0 +1,77 @@ +# Aceptar/Rechazar Solicitud de Servicio + +## Tipo de usuario +Veterinario + +## Donde empieza +Dashboard veterinario `/vet/` -> Seccion "Solicitudes pendientes" + +## Que quiere hacer el usuario +Revisar las solicitudes de servicio en su zona y decidir si las acepta o rechaza + +## Pasos - Aceptar solicitud + +1. Ingresar al dashboard veterinario +2. Ver listado de solicitudes pendientes en su zona de cobertura +3. Click en una solicitud para ver detalle: + - Datos del dueno (nombre, telefono, direccion) + - Datos de la mascota (nombre, tipo, edad, historial previo) + - Servicios solicitados + - Fechas preferidas por el dueno +4. Verificar disponibilidad en esas fechas +5. Click en "Aceptar" +6. Seleccionar fecha y hora disponible +7. Confirmar aceptacion + +## Pasos - Rechazar solicitud + +1. Ver detalle de la solicitud +2. Click en "Rechazar" +3. Opcionalmente seleccionar motivo: + - No tengo disponibilidad + - Fuera de mi zona + - No realizo este servicio + - Otro +4. Confirmar rechazo + +## Que deberia pasar + +Al aceptar: +- Solicitud pasa a estado "Coordinado" +- Vet queda asignado a la solicitud +- Dueno recibe notificacion con fecha/hora y datos del vet +- Aparece en el calendario del vet + +Al rechazar: +- Solicitud sigue disponible para otros vets +- Si la rechazo yo, ya no la veo en mi lista + +## Problemas comunes + +- Multiples vets aceptan la misma solicitud casi simultaneamente +- El vet acepta pero el dueno no puede en esa fecha +- No hay suficientes vets en la zona y la solicitud queda sin atender +- Vet acepta por error y quiere deshacer +- Las fechas preferidas ya pasaron + +## Casos especiales + +- Solicitud con mascota que el vet ya atendio antes: mostrar historial +- Solicitud urgente/emergencia: destacar visualmente +- Vet con agenda completa: no deberia poder aceptar +- Solicitud con multiples mascotas: revisar tiempo necesario +- Zona limite: solicitud que aparece para vets de zonas adyacentes + +## Flujos relacionados + +- Ver mi agenda (despues de aceptar) +- Realizar visita +- Cancelar visita aceptada + +## Notas tecnicas + +- APIs: + - `GET /solicitudes/api/v1/service-requests/?state=pending&veterinarian_area=X` + - `PATCH /solicitudes/api/v1/service-requests/{id}/` (aceptar/rechazar) +- Race condition: backend debe manejar caso de aceptacion simultanea +- Push notification al dueno cuando se acepta diff --git a/atlas/book/feature-form-samples/feature-form/veterinarian/02-gestion-agenda.md b/atlas/book/feature-form-samples/feature-form/veterinarian/02-gestion-agenda.md new file mode 100644 index 0000000..c02d669 --- /dev/null +++ b/atlas/book/feature-form-samples/feature-form/veterinarian/02-gestion-agenda.md @@ -0,0 +1,87 @@ +# Gestion de Agenda + +## Tipo de usuario +Veterinario + +## Donde empieza +Dashboard veterinario `/vet/` -> Menu "Mi agenda" o `/vet/turnos` + +## Que quiere hacer el usuario +Ver su calendario de visitas, gestionar disponibilidad y marcar dias no disponibles + +## Pasos - Ver agenda + +1. Ir a "Mi agenda" +2. Ver calendario con: + - Visitas confirmadas (con detalle al hacer click) + - Dias/horarios bloqueados + - Disponibilidad por franja horaria +3. Navegar entre semanas/meses +4. Filtrar por estado (pendientes, confirmadas, completadas) + +## Pasos - Configurar disponibilidad semanal + +1. Ir a "Configuracion" -> "Mi disponibilidad" +2. Para cada dia de la semana: + - Marcar si trabajo ese dia + - Definir franja horaria (ej: 9:00-18:00) + - Definir pausa (ej: 13:00-14:00) +3. Guardar configuracion + +## Pasos - Bloquear dias especificos + +1. En el calendario, click en un dia +2. Seleccionar "Marcar como no disponible" +3. Opcionalmente agregar motivo (vacaciones, curso, etc.) +4. Confirmar +5. Ese dia no aparece como opcion para nuevas solicitudes + +## Pasos - Ver detalle de visita + +1. Click en una visita en el calendario +2. Ver informacion completa: + - Dueno: nombre, telefono, direccion + - Mascota: nombre, tipo, edad, foto + - Servicios a realizar + - Historial previo de esa mascota + - Estado de pago + - Notas especiales + +## Que deberia pasar + +- Calendario siempre actualizado con visitas +- No puedo recibir solicitudes en dias bloqueados +- Cambios de disponibilidad afectan solo solicitudes futuras +- Puedo ver toda la informacion necesaria para la visita + +## Problemas comunes + +- Vet olvida desbloquear dia despues de terminar vacaciones +- Visitas muy seguidas en zonas distantes (logistica) +- Vet quiere mover visita a otro horario +- Visita cancelada a ultimo momento +- Vet no marco indisponibilidad y le llegan solicitudes que no puede atender + +## Casos especiales + +- Visita reprogramada: mostrar historial de cambios +- Visita cancelada por el dueno: liberar espacio en agenda +- Emergencia: poder agregar visita fuera de horario normal +- Feriados: bloquear automaticamente? +- Multiples visitas mismo dia/zona: optimizar ruta? + +## Flujos relacionados + +- Aceptar solicitud (agrega a la agenda) +- Realizar visita (inicia desde la agenda) +- Gestionar zonas de cobertura + +## Notas tecnicas + +- APIs: + - `GET /mascotas/api/v1/vet-visits/?veterinarian=X` + - `GET /mascotas/api/v1/vet_availabilities/` + - `PUT /mascotas/api/v1/vet_availabilities/{id}/` + - `POST /mascotas/api/v1/vet_unavailabilities/` (dias bloqueados) +- Vista calendario: usar libreria tipo FullCalendar +- Colores por estado: pendiente (amarillo), pagado (verde), completado (azul) diff --git a/atlas/book/feature-form-samples/feature-form/veterinarian/03-realizar-visita.md b/atlas/book/feature-form-samples/feature-form/veterinarian/03-realizar-visita.md new file mode 100644 index 0000000..d3eeb28 --- /dev/null +++ b/atlas/book/feature-form-samples/feature-form/veterinarian/03-realizar-visita.md @@ -0,0 +1,97 @@ +# Realizar Visita y Crear Informe + +## Tipo de usuario +Veterinario + +## Donde empieza +Agenda del dia -> Visita programada -> "Iniciar visita" o `/vet/visita/{id}` + +## Que quiere hacer el usuario +Registrar la visita medica, crear el informe clinico y completar el flujo + +## Pasos - Antes de la visita + +1. Ver agenda del dia +2. Click en la visita programada +3. Ver datos del dueno (direccion, telefono) para llegar +4. Ver datos de la mascota e historial +5. Preparar insumos segun servicios solicitados + +## Pasos - Iniciar visita + +1. Al llegar al domicilio, click "Iniciar visita" +2. Estado cambia a "En progreso" +3. Se registra hora de inicio + +## Pasos - Crear informe medico + +1. Durante o despues de la atencion, ir a "Crear informe" +2. Completar examen fisico: + - Peso actual + - Temperatura + - Frecuencia cardiaca + - Frecuencia respiratoria + - Estado general (mucosas, hidratacion, etc.) +3. Registrar hallazgos clinicos +4. Escribir diagnostico +5. Indicar tratamiento: + - Descripcion del tratamiento + - Medicamentos recetados (nombre, dosis, frecuencia, duracion) + - Indicaciones especiales +6. Solicitar estudios si es necesario: + - Tipo de estudio + - Laboratorio sugerido +7. Programar seguimiento: + - Fecha sugerida de control + - Motivo del seguimiento +8. Agregar observaciones adicionales +9. Guardar informe + +## Pasos - Completar visita + +1. Verificar que el informe este guardado +2. Click en "Completar visita" +3. Confirmar finalizacion +4. Se genera factura automaticamente (AFIP) +5. Dueno recibe notificacion con acceso al informe + +## Que deberia pasar + +- Informe queda guardado y asociado a la mascota +- Dueno puede ver el informe desde su cuenta +- Se genera factura electronica +- Visita se marca como "Completada" +- Vet puede ver la visita en su historial + +## Problemas comunes + +- Vet olvida completar el informe y cierra la visita +- Mala conexion a internet durante la visita +- Informe muy largo y se pierde por timeout +- Dueno no esta en el domicilio +- Mascota agresiva, no se puede atender +- Necesita derivar a clinica + +## Casos especiales + +- Visita cancelada in situ: registrar motivo, posible cargo +- Emergencia durante visita rutinaria: agregar servicios +- Multiples mascotas en la visita: un informe por cada una +- Vet necesita segundo opinion: derivar a especialista +- Fallecimiento durante la visita: protocolo especial + +## Flujos relacionados + +- Ver historial de paciente (antes de atender) +- Cobro de la visita (Mercado Pago del vet) +- Seguimiento post-visita + +## Notas tecnicas + +- APIs: + - `PATCH /mascotas/api/v1/vet-visits/{id}/` (cambiar estado) + - `POST /mascotas/api/v1/vetvisitreport/` (crear informe) + - `GET /mascotas/api/v1/vet-visits/?pet=X` (historial previo) +- Integracion AFIP via django_afip +- Guardado automatico del informe cada 30 segundos +- Modo offline: guardar localmente y sincronizar despues diff --git a/atlas/book/feature-form-samples/feature-form/veterinarian/04-zonas-cobertura.md b/atlas/book/feature-form-samples/feature-form/veterinarian/04-zonas-cobertura.md new file mode 100644 index 0000000..7601d92 --- /dev/null +++ b/atlas/book/feature-form-samples/feature-form/veterinarian/04-zonas-cobertura.md @@ -0,0 +1,66 @@ +# Gestionar Zonas de Cobertura + +## Tipo de usuario +Veterinario + +## Donde empieza +Dashboard veterinario -> Menu "Mi cobertura" o Perfil -> "Zonas de atencion" + +## Que quiere hacer el usuario +Definir en que barrios/zonas esta dispuesto a atender visitas a domicilio + +## Pasos - Ver zonas actuales + +1. Ir a "Mi cobertura" +2. Ver mapa con zonas actualmente cubiertas +3. Ver listado de barrios seleccionados + +## Pasos - Agregar zona + +1. En el mapa o listado, buscar el barrio +2. Click en el barrio para seleccionarlo +3. El barrio se agrega a mi cobertura +4. Guardar cambios + +## Pasos - Quitar zona + +1. En mi listado de zonas +2. Click en "X" o deseleccionar el barrio +3. Confirmar eliminacion +4. Guardar cambios + +## Que deberia pasar + +- Solo recibo solicitudes de las zonas que seleccione +- Si agrego zona, empiezo a ver solicitudes pendientes de esa zona +- Si quito zona, dejo de ver nuevas solicitudes de esa zona +- Visitas ya aceptadas no se afectan + +## Problemas comunes + +- Vet quiere zona muy especifica (solo algunas calles) +- Zonas con poca demanda, vet no recibe solicitudes +- Zonas con mucha demanda, vet saturado +- Limites de zona confusos (calle pertenece a 2 barrios) +- Vet quiere cobertura condicional (solo ciertos dias) + +## Casos especiales + +- Zona nueva no listada: solicitar a ops que la agregue +- Cobertura temporal (ej: cubriendo a otro vet) +- Cobertura por horario (mañana en zona A, tarde en zona B) +- Vet se muda: actualizar todas las zonas + +## Flujos relacionados + +- Aceptar solicitud (solo veo solicitudes de mis zonas) +- Ver agenda (optimizar ruta por zonas) + +## Notas tecnicas + +- APIs: + - `GET /common/api/v1/neighborhoods/` (lista de barrios) + - `GET /mascotas/api/v1/veterinarians/{id}/` (zonas actuales) + - `PATCH /mascotas/api/v1/veterinarians/{id}/` (actualizar zonas) +- Modelo: Veterinarian tiene M2M con Neighborhood +- Coeficiente de distancia por barrio para calcular viabilidad diff --git a/atlas/book/feature-form-samples/feature-form/veterinarian/05-historial-pacientes.md b/atlas/book/feature-form-samples/feature-form/veterinarian/05-historial-pacientes.md new file mode 100644 index 0000000..0944535 --- /dev/null +++ b/atlas/book/feature-form-samples/feature-form/veterinarian/05-historial-pacientes.md @@ -0,0 +1,81 @@ +# Ver Historial de Pacientes + +## Tipo de usuario +Veterinario + +## Donde empieza +Dashboard veterinario -> "Historia clinica" o `/vet/historia-clinica` + +## Que quiere hacer el usuario +Buscar y revisar el historial medico de pacientes que ha atendido + +## Pasos - Buscar paciente + +1. Ir a "Historia clinica" +2. Usar el buscador: + - Por nombre del dueno + - Por nombre de la mascota + - Por telefono + - Por email +3. Ver resultados de busqueda +4. Click en el paciente deseado + +## Pasos - Ver historial de mascota + +1. Seleccionar la mascota +2. Ver ficha de la mascota: + - Datos basicos (nombre, tipo, raza, edad) + - Foto + - Peso historico + - Estado de vacunacion +3. Ver listado de visitas (todas, no solo las mias) +4. Click en una visita para ver el informe completo + +## Pasos - Revisar informe anterior + +1. En el listado de visitas, click en una +2. Ver informe completo: + - Fecha y veterinario que atendio + - Examen fisico + - Diagnostico + - Tratamiento + - Medicamentos indicados + - Estudios solicitados + - Seguimiento + +## Que deberia pasar + +- Puedo ver historial completo de pacientes que atendi +- Puedo ver informes de otros vets (para continuidad de atencion) +- Tengo contexto para tomar decisiones clinicas + +## Problemas comunes + +- Busqueda no encuentra al paciente (nombre mal escrito) +- Mascota atendida por otro vet, no tengo acceso? +- Historial muy extenso, dificil encontrar info relevante +- Informes anteriores incompletos o ilegibles +- Duplicados de pacientes (mismo dueno registrado 2 veces) + +## Casos especiales + +- Mascota nueva sin historial +- Mascota atendida en otra clinica (historial externo) +- Dueno tiene multiples mascotas +- Mascota transferida a otro dueno +- Paciente de urgencia/emergencia sin cita previa + +## Flujos relacionados + +- Realizar visita (consultar historial antes/durante) +- Aceptar solicitud (ver si ya lo atendi antes) + +## Notas tecnicas + +- APIs: + - `GET /mascotas/api/v1/pet-owners/?search=X` + - `GET /mascotas/api/v1/pets/?pet_owner=X` + - `GET /mascotas/api/v1/vet-visits/?pet=X` + - `GET /mascotas/api/v1/vetvisitreport/?vet_visit=X` +- Permisos: vet puede ver historial de cualquier paciente que haya atendido +- Busqueda: usar indice de texto completo para rendimiento diff --git a/atlas/book/feature-form-samples/index.html b/atlas/book/feature-form-samples/index.html new file mode 100644 index 0000000..bc91620 --- /dev/null +++ b/atlas/book/feature-form-samples/index.html @@ -0,0 +1,143 @@ + + + + + + Ops Templates Sample - Album + + + +
+
+ +

Ops Templates Sample

+

User flow documentation templates - non-technical format for support/ops teams

+ Sample Data +
+ + + + + + + +
+

Template Structure

+

Each template includes: User type, Entry point, Goal, Steps, Expected result, Common problems, Edge cases, Related flows, Technical notes.

+

These templates transform into Gherkin specs for testing.

+
+ + +
+ + diff --git a/atlas/book/feature-form-samples/template/feature-form.md b/atlas/book/feature-form-samples/template/feature-form.md new file mode 100644 index 0000000..de3c18c --- /dev/null +++ b/atlas/book/feature-form-samples/template/feature-form.md @@ -0,0 +1,91 @@ +# Plantilla: Documentacion de Flujos + +## Para: Equipo de Soporte/Operaciones + +Usa esta plantilla para documentar cualquier accion/flujo del sistema. + +--- + +## Plantilla + +``` +### [Nombre del Flujo] + +**Tipo de usuario:** [Dueno de mascota / Veterinario / Admin] + +**Donde empieza:** [Que pagina/boton/link] + +**Que quiere hacer el usuario:** [Objetivo en una oracion] + +**Pasos:** +1. [Primera cosa que hace el usuario] +2. [Segunda cosa que hace el usuario] +3. [etc.] + +**Que deberia pasar:** [Resultado esperado cuando todo funciona] + +**Problemas comunes:** +- [Problema 1] +- [Problema 2] + +**Casos especiales:** +- [Caso especial 1] +- [Caso especial 2] + +**Flujos relacionados:** [Otros flujos que se conectan con este] +``` + +--- + +## Ejemplo Completo + +### Reservar turno de vacunacion + +**Tipo de usuario:** Dueno de mascota + +**Donde empieza:** Pagina principal -> Boton "Agendar visita" + +**Que quiere hacer el usuario:** Reservar un turno de vacunacion para su gato + +**Pasos:** +1. Click en "Agendar visita" en la pagina principal +2. Ingresar direccion en el popup +3. Click en "Siguiente" +4. Completar datos mascota: nombre, tipo (gato), edad +5. Seleccionar categoria "Vacunacion" +6. Elegir la vacuna especifica +7. Ver que "Consulta clinica" se agrega automaticamente +8. Elegir fechas preferidas +9. Ingresar datos de contacto (nombre, telefono, email) +10. Enviar la solicitud + +**Que deberia pasar:** +- Ver mensaje de confirmacion +- Recibir email de confirmacion +- La solicitud aparece en "Mis turnos" con estado "Pendiente" + +**Problemas comunes:** +- Los usuarios intentan quitar "Consulta clinica" y no pueden +- El autocompletado de direcciones a veces no encuentra su calle +- Algunos usuarios no ven el link "Ya soy cliente" y crean cuentas duplicadas + +**Casos especiales:** +- Si la mascota ya esta castrada, el servicio de castracion no deberia aparecer +- Si el usuario abandona a mitad del flujo, sus datos se guardan como "invitado" +- Algunos barrios no tienen cobertura - deberia mostrar un error claro + +**Flujos relacionados:** +- "Ver mis turnos" (para ver el estado despues de reservar) +- "Pagar turno" (cuando se solicita el pago) + +--- + +## Preguntas a Considerar + +1. **Camino feliz:** Que pasa cuando todo funciona perfecto? +2. **Validacion:** Que errores puede ver el usuario? +3. **Permisos:** Quien puede hacer esto? +4. **Estados:** Esta accion cambia segun algun estado? +5. **Dependencias:** Esto requiere que algo mas haya pasado antes? +6. **Efectos secundarios:** Esto dispara emails, notificaciones? +7. **Deshacer:** El usuario puede revertir esta accion? diff --git a/atlas/book/gherkin-samples/CLAUDE.md b/atlas/book/gherkin-samples/CLAUDE.md new file mode 100644 index 0000000..1b58a54 --- /dev/null +++ b/atlas/book/gherkin-samples/CLAUDE.md @@ -0,0 +1,114 @@ +# Gherkin Sample Book + +## Purpose +**SAMPLE DATA** - Example BDD/Gherkin `.feature` files for demonstration and testing purposes. These show realistic examples of how feature files will look when actual ops templates are defined. + +Use these to: +- Understand Gherkin structure and patterns +- Compare Spanish vs English keywords +- Test tooling (behave, pytest-bdd, playwright-bdd) +- Demo the feature-flow pipeline + +Real feature files will be derived from validated ops templates. + +## Structure + +``` +gherkin-sample/ +├── es/ # Spanish keywords (Dado/Cuando/Entonces) +│ ├── pet-owner/ +│ ├── veterinarian/ +│ └── backoffice/ +└── en/ # English keywords (Given/When/Then) + ├── pet-owner/ + ├── veterinarian/ + └── backoffice/ +``` + +## Language Versions + +**Spanish Keywords (`es/`)** +- Uses `# language: es` directive +- Keywords: Característica, Escenario, Dado, Cuando, Entonces, Y, Pero +- Keywords: Esquema del escenario, Ejemplos, Antecedentes, Regla +- For teams preferring Spanish BDD syntax + +**English Keywords (`en/`)** +- Standard Gherkin syntax +- Keywords: Feature, Scenario, Given, When, Then, And, But +- Keywords: Scenario Outline, Examples, Background, Rule +- Same Spanish content, different keywords + +## Feature Files + +### Pet Owner (5 files) +| File | Description | +|------|-------------| +| 01-registro.feature | User registration flow | +| 02-reservar-turno.feature | Book appointment (turnero) | +| 03-gestion-mascotas.feature | Pet management | +| 04-pago-turno.feature | Payment flow | +| 05-historial-medico.feature | Medical history | + +### Veterinarian (5 files) +| File | Description | +|------|-------------| +| 01-aceptar-solicitud.feature | Accept/reject requests | +| 02-gestion-agenda.feature | Schedule management | +| 03-realizar-visita.feature | Conduct visit & report | +| 04-zonas-cobertura.feature | Coverage area management | +| 05-historial-pacientes.feature | Patient history | + +### Backoffice (5 files) +| File | Description | +|------|-------------| +| 01-gestion-solicitudes.feature | Request management | +| 02-gestion-usuarios.feature | User management | +| 03-gestion-servicios.feature | Services & pricing | +| 04-reembolsos.feature | Refund process | +| 05-reportes.feature | Reports & dashboard | + +## Gherkin Patterns Used + +### Basic +- Feature, Scenario, Given/When/Then + +### Intermediate +- **Background**: Shared setup for all scenarios +- **Scenario Outline + Examples**: Test variations without duplication +- **Data Tables**: Multiple items in one step + +### Advanced +- **Rule**: Group related scenarios +- **Doc Strings**: Large text blocks (diagnoses, reports) +- **Tags**: For filtering (@smoke, @critical, @wip) + +## Comment Headers + +Each file includes metadata comments: +```gherkin +# Fuente: album/book/ops-templates/... +# Drive: [Google Drive reference] +# Tests Backend: pytest tests/contracts/... +# Tests Frontend: npx playwright test ... +``` + +## Tools + +To run/validate these files: + +**Python** +- behave +- pytest-bdd + +**JavaScript/TypeScript** +- playwright-bdd +- jest-cucumber + +**IDE Support** +- VS Code: Cucumber (Gherkin) Full Support extension +- JetBrains: Built-in Gherkin plugin + +## Source Templates + +Sample ops templates at: `album/book/ops-templates-sample/` diff --git a/atlas/book/gherkin-samples/detail.html b/atlas/book/gherkin-samples/detail.html new file mode 100644 index 0000000..4752009 --- /dev/null +++ b/atlas/book/gherkin-samples/detail.html @@ -0,0 +1,161 @@ + + + + + + {{ filename }} - Gherkin + + + + + +
+
+ +

{{ filename.replace('.feature', '').replace('-', ' ').title() }}

+
+ {{ 'Espanol' if lang == 'es' else 'English' }} + {{ user_type }} + Sample +
+
+ +
+
+ {{ filename }} + +
+
{{ content }}
+
+ + + + +
+ + + + + + + diff --git a/atlas/book/gherkin-samples/en/backoffice/01-gestion-solicitudes.feature b/atlas/book/gherkin-samples/en/backoffice/01-gestion-solicitudes.feature new file mode 100644 index 0000000..72c1400 --- /dev/null +++ b/atlas/book/gherkin-samples/en/backoffice/01-gestion-solicitudes.feature @@ -0,0 +1,104 @@ +# Fuente: album/book/ops-templates/backoffice/01-gestion-solicitudes.md +# Drive: 05. ATC - Operaciones/Procedimientos +# Tests Backend: pytest tests/contracts/solicitudes/test_service_requests.py +# Tests Frontend: npx playwright test admin-requests.spec.ts + +Feature: Gestion de solicitudes de servicio + + Como administrador del backoffice + Quiero gestionar todas las solicitudes del sistema + Para asegurar que se atiendan correctamente + + Background: + Given que estoy logueado como administrador + And estoy en la seccion "Solicitudes" del backoffice + + # ============================================ + # VER Y FILTRAR SOLICITUDES + # ============================================ + + Scenario: Ver listado de todas las solicitudes + When cargo la pagina de solicitudes + Then deberia ver un listado paginado + And cada solicitud deberia mostrar: + | campo | + | ID | + | Dueno | + | Mascota | + | Servicios | + | Estado | + | Fecha | + + Scenario Outline: Filtrar solicitudes por estado + When filtro por estado "" + Then solo deberia ver solicitudes con estado "" + + Examples: + | estado | + | Pendiente | + | Coordinado | + | Pagado | + | Completado | + | Cancelado | + + # ============================================ + # ASIGNAR VETERINARIO MANUALMENTE + # ============================================ + + Scenario: Asignar veterinario a solicitud pendiente + Given que hay una solicitud pendiente en "Palermo" + And no fue aceptada por ningun veterinario + When abro el detalle de la solicitud + And hago click en "Asignar veterinario" + Then deberia ver lista de veterinarios con cobertura en Palermo + + When selecciono "Dra. Garcia" + And selecciono fecha "15 de enero" hora "10:00" + And confirmo la asignacion + Then la solicitud deberia pasar a estado "Coordinado" + And deberia estar asignada a Dra. Garcia + And el dueno deberia recibir notificacion + And el veterinario deberia recibir notificacion + + # ============================================ + # CAMBIAR ESTADO MANUALMENTE + # ============================================ + + Scenario: Cambiar estado de solicitud + Given que hay una solicitud en estado "Coordinado" + And el pago se proceso pero el webhook fallo + When abro la solicitud + And hago click en "Cambiar estado" + And selecciono "Pagado" + And ingreso motivo "Pago confirmado manualmente - ID MP: 12345" + And confirmo el cambio + Then la solicitud deberia pasar a "Pagado" + And el cambio deberia registrarse en el historial + + Rule: Solo ciertos cambios de estado son validos + + Scenario: No puedo volver a estado anterior + Given que hay una solicitud en estado "Completado" + When intento cambiar el estado a "Pagado" + Then deberia ver error "No se puede volver a un estado anterior" + + Scenario: Puedo cancelar desde cualquier estado + Given que hay una solicitud en estado "Coordinado" + When cambio el estado a "Cancelado" + And ingreso motivo de cancelacion + Then la solicitud deberia cancelarse + + # ============================================ + # CASOS ESPECIALES + # ============================================ + + Scenario: Reasignar veterinario + Given que hay una solicitud asignada a "Dr. Lopez" + And Dr. Lopez no puede asistir + When abro la solicitud + And hago click en "Reasignar" + And selecciono otro veterinario + And confirmo + Then Dr. Lopez deberia ser notificado de la desasignacion + And el nuevo vet deberia ser notificado + And el dueno deberia ser notificado del cambio diff --git a/atlas/book/gherkin-samples/en/backoffice/02-gestion-usuarios.feature b/atlas/book/gherkin-samples/en/backoffice/02-gestion-usuarios.feature new file mode 100644 index 0000000..75224f2 --- /dev/null +++ b/atlas/book/gherkin-samples/en/backoffice/02-gestion-usuarios.feature @@ -0,0 +1,88 @@ +# Fuente: album/book/ops-templates/backoffice/02-gestion-usuarios.md +# Drive: 05. ATC - Operaciones/Procedimientos +# Tests Backend: pytest tests/contracts/mascotas/test_pet_owners.py +# Tests Frontend: npx playwright test admin-users.spec.ts + +Feature: Gestion de usuarios + + Como administrador del backoffice + Quiero gestionar duenos y veterinarios + Para mantener la base de usuarios actualizada + + Background: + Given que estoy logueado como administrador + And estoy en el backoffice + + # ============================================ + # GESTION DE DUENOS + # ============================================ + + Scenario: Buscar dueno de mascota + Given que estoy en la seccion "Duenos" + When busco "maria@ejemplo.com" + Then deberia ver a Maria Garcia en los resultados + And deberia ver sus mascotas listadas + + Scenario: Ver perfil completo de dueno + Given que encontre a "Maria Garcia" + When hago click en su perfil + Then deberia ver: + | seccion | contenido | + | Datos personales | Nombre, email, telefono | + | Direcciones | Direcciones registradas | + | Mascotas | Lista de mascotas | + | Historial | Solicitudes anteriores | + + Scenario: Editar datos de dueno + Given que estoy viendo el perfil de un dueno + When hago click en "Editar" + And cambio el telefono a "1155559999" + And guardo los cambios + Then el telefono deberia actualizarse + + # ============================================ + # GESTION DE VETERINARIOS + # ============================================ + + Scenario: Agregar nuevo veterinario + Given que estoy en la seccion "Veterinarios" + When hago click en "Agregar veterinario" + And completo los datos: + | campo | valor | + | Nombre | Dr. Juan Perez | + | Matricula | MV-12345 | + | Email | jperez@ejemplo.com | + | Telefono | 1155551234 | + And selecciono zonas de cobertura: + | zona | + | Palermo | + | Recoleta | + And genero credenciales de acceso + And guardo + Then deberia crearse el veterinario + And deberia poder loguearse con sus credenciales + + Scenario: Intentar desactivar veterinario con citas pendientes + Given que el veterinario "Dra. Garcia" tiene 3 citas pendientes + When intento desactivarla + Then deberia ver advertencia "Tiene 3 citas pendientes" + And deberia ver opciones: + | opcion | + | Reasignar citas y desactivar | + | Cancelar | + + # ============================================ + # CASOS ESPECIALES + # ============================================ + + Scenario: Fusionar cuentas duplicadas + Given que hay dos cuentas para el mismo dueno: + | cuenta | email | mascotas | + | Cuenta1 | maria@gmail.com | Luna | + | Cuenta2 | maria@hotmail.com | Rocky | + When selecciono ambas cuentas + And hago click en "Fusionar" + And elijo Cuenta1 como principal + And confirmo + Then deberia existir solo Cuenta1 + And deberia tener ambas mascotas diff --git a/atlas/book/gherkin-samples/en/backoffice/03-gestion-servicios.feature b/atlas/book/gherkin-samples/en/backoffice/03-gestion-servicios.feature new file mode 100644 index 0000000..2cf27a0 --- /dev/null +++ b/atlas/book/gherkin-samples/en/backoffice/03-gestion-servicios.feature @@ -0,0 +1,91 @@ +# Fuente: album/book/ops-templates/backoffice/03-gestion-servicios.md +# Drive: 08. IT y Producto/Catalogo Servicios +# Tests Backend: pytest tests/contracts/productos/test_services.py +# Tests Frontend: npx playwright test admin-services.spec.ts + +Feature: Gestion de servicios y precios + + Como administrador + Quiero gestionar el catalogo de servicios + Para mantener la oferta actualizada + + Background: + Given que estoy logueado como administrador + And estoy en la seccion "Servicios" + + # ============================================ + # VER CATALOGO DE SERVICIOS + # ============================================ + + Scenario: Ver listado de servicios + When cargo la pagina de servicios + Then deberia ver todos los servicios organizados por categoria + And cada servicio deberia mostrar: + | campo | + | Nombre | + | Categoria | + | Tipo mascota | + | Precio actual | + | Estado | + + # ============================================ + # CREAR Y EDITAR SERVICIOS + # ============================================ + + Scenario: Agregar nuevo servicio + When hago click en "Agregar servicio" + And completo los datos: + | campo | valor | + | Nombre | Vacuna Quintuple Felina | + | Descripcion | Protege contra 5 enfermedades | + | Categoria | Vacunacion | + | Tipo mascota | Gato | + | Precio | 15000 | + And guardo el servicio + Then el servicio deberia crearse + And deberia aparecer en el turnero para gatos + + Scenario: Desactivar servicio + Given que existe el servicio "Servicio Antiguo" + When abro el servicio + And hago click en "Desactivar" + And confirmo + Then el servicio no deberia aparecer en el turnero + + # ============================================ + # GESTION DE PRECIOS + # ============================================ + + Scenario: Actualizar precio de servicio + Given que "Consulta clinica" tiene precio actual de 10000 + When abro el servicio + And voy a la seccion "Precios" + And hago click en "Agregar precio" + And ingreso nuevo precio 12000 + And selecciono fecha de vigencia "01/02/2024" + And guardo + Then deberia crearse el nuevo precio + And el precio anterior deberia quedar en historial + + Rule: El precio se congela al crear la solicitud + + Scenario: Cambio de precio no afecta solicitudes existentes + Given que hay una solicitud pendiente con "Consulta clinica" a 10000 + When cambio el precio de "Consulta clinica" a 12000 + Then la solicitud deberia mantener el precio de 10000 + + # ============================================ + # CONFIGURAR COMBOS + # ============================================ + + Scenario: Crear combo de servicios + When voy a "Combos" + And hago click en "Agregar combo" + And configuro: + | campo | valor | + | Nombre | Plan Preventivo Felino | + | Servicios | Vacunacion, Desparasitacion | + | Precio combo | 25000 | + And guardo + Then el combo deberia crearse + And deberia aplicarse automaticamente en el turnero diff --git a/atlas/book/gherkin-samples/en/backoffice/04-reembolsos.feature b/atlas/book/gherkin-samples/en/backoffice/04-reembolsos.feature new file mode 100644 index 0000000..118b2af --- /dev/null +++ b/atlas/book/gherkin-samples/en/backoffice/04-reembolsos.feature @@ -0,0 +1,86 @@ +# Fuente: album/book/ops-templates/backoffice/04-reembolsos.md +# Drive: 07. Finanzas y contabilidad/Reembolsos +# Tests Backend: pytest tests/contracts/payments/test_refunds.py +# Tests Frontend: npx playwright test admin-refunds.spec.ts + +Feature: Proceso de reembolso + + Como administrador + Quiero procesar reembolsos de pagos + Para resolver cancelaciones y problemas + + Background: + Given que estoy logueado como administrador + And estoy en el backoffice + + # ============================================ + # PROCESAR REEMBOLSO + # ============================================ + + Scenario: Reembolso total exitoso + Given que hay una solicitud pagada por 15000 + And la visita fue cancelada + When abro la solicitud + And hago click en "Procesar reembolso" + And selecciono "Reembolso total" + And ingreso motivo "Cancelacion por indisponibilidad del veterinario" + And confirmo el reembolso + Then deberia enviarse la solicitud de reembolso a Mercado Pago + And deberia ver mensaje "Reembolso en proceso" + And el dueno deberia recibir email de confirmacion + + Scenario: Reembolso parcial + Given que hay una solicitud pagada por 20000 + And solo se realizo parte del servicio + When proceso reembolso parcial por 10000 + And ingreso motivo "Servicio parcialmente completado" + And confirmo + Then deberia procesarse reembolso por 10000 + And deberia quedar registro del monto reembolsado + + # ============================================ + # TIEMPOS DE ACREDITACION + # ============================================ + + Scenario Outline: Informar tiempo de acreditacion segun metodo + Given que el pago original fue con "" + When proceso el reembolso + Then deberia informar al usuario: + """ + El reembolso se acreditara en + """ + + Examples: + | metodo | tiempo | + | Tarjeta credito | 1-2 resumenes de cuenta | + | Tarjeta debito | 5-10 dias habiles | + | Dinero en cuenta MP | forma inmediata | + + # ============================================ + # VALIDACIONES + # ============================================ + + Scenario: No puedo reembolsar mas del monto pagado + Given que hay una solicitud pagada por 15000 + When intento reembolsar 20000 + Then deberia ver error "El monto supera el pago original" + + Scenario: Reembolso duplicado + Given que ya procese un reembolso total para una solicitud + When intento procesar otro reembolso + Then deberia ver error "Esta solicitud ya fue reembolsada" + + Scenario: Pago fuera de plazo de reembolso + Given que hay un pago de hace 200 dias + When intento reembolsar + Then deberia ver advertencia "Fuera de plazo de reembolso de MP (180 dias)" + + # ============================================ + # SEGUIMIENTO + # ============================================ + + Scenario: Ver estado del reembolso + Given que procese un reembolso hace 2 dias + When veo el detalle del reembolso + Then deberia ver el estado actual en Mercado Pago + And deberia ver historial de estados diff --git a/atlas/book/gherkin-samples/en/backoffice/05-reportes.feature b/atlas/book/gherkin-samples/en/backoffice/05-reportes.feature new file mode 100644 index 0000000..43032a0 --- /dev/null +++ b/atlas/book/gherkin-samples/en/backoffice/05-reportes.feature @@ -0,0 +1,97 @@ +# Fuente: album/book/ops-templates/backoffice/05-reportes.md +# Drive: 07. Finanzas y contabilidad/Reportes +# Tests Backend: pytest tests/contracts/mascotas/test_stats.py +# Tests Frontend: npx playwright test admin-reports.spec.ts + +Feature: Reportes y dashboard + + Como administrador o gerente + Quiero ver metricas y generar reportes + Para tomar decisiones informadas + + Background: + Given que estoy logueado como administrador + And estoy en el backoffice + + # ============================================ + # DASHBOARD + # ============================================ + + Scenario: Ver dashboard principal + When accedo al dashboard + Then deberia ver metricas resumidas: + | metrica | periodo | + | Solicitudes nuevas | Hoy | + | Visitas completadas | Semana | + | Ingresos | Mes | + | Veterinarios activos | Actual | + | Tasa de conversion | Mes | + + Scenario: Ver grafico de tendencia + When veo el grafico de solicitudes + Then deberia ver la evolucion de los ultimos 30 dias + And deberia poder comparar con periodo anterior + + # ============================================ + # REPORTE DE SOLICITUDES + # ============================================ + + Scenario: Generar reporte de solicitudes + When voy a "Reportes" -> "Solicitudes" + And selecciono periodo "Enero 2024" + And hago click en "Generar" + Then deberia ver tabla con solicitudes del periodo + And deberia ver totales por estado + + Scenario: Filtrar reporte por multiples criterios + When genero reporte con filtros: + | filtro | valor | + | Periodo | Enero 2024 | + | Zona | Palermo | + | Veterinario | Dra. Garcia | + Then deberia ver solo solicitudes que cumplan todos los criterios + + # ============================================ + # REPORTE DE INGRESOS + # ============================================ + + Scenario: Ver ingresos por periodo + When voy a "Reportes" -> "Ingresos" + And selecciono "Ultimo trimestre" + Then deberia ver: + | dato | + | Ingresos totales | + | Cantidad de pagos | + | Ticket promedio | + | Reembolsos realizados | + | Ingreso neto | + + Scenario: Ingresos agrupados por veterinario + When agrupo el reporte por "Veterinario" + Then deberia ver para cada vet: + | dato | + | Visitas completadas | + | Ingresos generados | + | Porcentaje del total | + + # ============================================ + # EXPORTACION + # ============================================ + + Scenario Outline: Exportar reporte en diferentes formatos + Given que tengo un reporte generado + When hago click en "Exportar" + And selecciono formato "" + Then deberia descargarse el archivo en formato + + Examples: + | formato | + | CSV | + | Excel | + | PDF | + + Scenario: Exportar reporte grande de forma asincrona + Given que genere un reporte con mas de 10000 registros + When hago click en "Exportar" + Then deberia ver mensaje "Generando exportacion..." + And deberia recibir notificacion cuando este listo diff --git a/atlas/book/gherkin-samples/en/pet-owner/01-registro.feature b/atlas/book/gherkin-samples/en/pet-owner/01-registro.feature new file mode 100644 index 0000000..1a97123 --- /dev/null +++ b/atlas/book/gherkin-samples/en/pet-owner/01-registro.feature @@ -0,0 +1,91 @@ +# Fuente: album/book/ops-templates/pet-owner/01-registro.md +# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario +# Tests Backend: pytest tests/contracts/common/test_users.py +# Tests Frontend: npx playwright test auth.spec.ts + +Feature: Registro de usuario + + Como visitante de la plataforma + Quiero poder crear una cuenta + Para gestionar mis mascotas y reservar turnos + + # ============================================ + # CAMINO FELIZ + # ============================================ + + Scenario: Registro exitoso con datos validos + Given que estoy en la pagina de registro + When ingreso email "nuevo@ejemplo.com" + And ingreso contraseña "Password123" + And confirmo contraseña "Password123" + And acepto los terminos y condiciones + And hago click en "Crear cuenta" + Then deberia ver mensaje "Te enviamos un email de verificacion" + And deberia recibir email de verificacion + + Scenario: Verificar email y activar cuenta + Given que me registre con email "nuevo@ejemplo.com" + And recibi el email de verificacion + When hago click en el link de verificacion + Then mi cuenta deberia estar activa + And deberia ser redirigido al dashboard + + # ============================================ + # VALIDACIONES + # ============================================ + + Scenario Outline: Registro con datos invalidos + Given que estoy en la pagina de registro + When ingreso email "" + And ingreso contraseña "" + And confirmo contraseña "" + And hago click en "Crear cuenta" + Then deberia ver error "" + + Examples: + | email | password | confirmacion | mensaje_error | + | invalido | Password123 | Password123 | Email invalido | + | test@test.com | 123 | 123 | Contraseña muy corta | + | test@test.com | password | password | Debe contener al menos un numero | + | test@test.com | Password123 | Diferente123 | Las contraseñas no coinciden | + + Scenario: Registro con email ya existente + Given que existe un usuario con email "existente@ejemplo.com" + And estoy en la pagina de registro + When ingreso email "existente@ejemplo.com" + And completo el resto del formulario correctamente + And hago click en "Crear cuenta" + Then deberia ver error "Este email ya esta registrado" + And deberia ver link "Recuperar contraseña" + + # ============================================ + # CASOS ESPECIALES + # ============================================ + + Scenario: Registro linkea con cuenta invitado existente + # Usuario que reservo turno como invitado y ahora quiere registrarse + Given que existe un usuario invitado con email "invitado@ejemplo.com" + And ese usuario tiene una mascota "Luna" registrada + And estoy en la pagina de registro + When me registro con email "invitado@ejemplo.com" + And verifico mi cuenta + Then deberia ver mi mascota "Luna" en el dashboard + And deberia ver mis turnos anteriores + + Scenario: Registro desde flujo de turnero + # Usuario empezo a reservar turno y decide crear cuenta + Given que estoy en el paso final del turnero + And ingrese mis datos de contacto + When hago click en "Crear cuenta para guardar mis datos" + Then deberia ver formulario simplificado + And mi email ya deberia estar pre-llenado + And solo deberia ingresar contraseña + + Scenario: Reenviar email de verificacion + Given que me registre pero no verifique mi cuenta + And estoy en la pagina de login + When intento iniciar sesion + Then deberia ver "Tu cuenta no esta verificada" + And deberia ver boton "Reenviar email" + When hago click en "Reenviar email" + Then deberia recibir nuevo email de verificacion diff --git a/atlas/book/gherkin-samples/en/pet-owner/02-reservar-turno.feature b/atlas/book/gherkin-samples/en/pet-owner/02-reservar-turno.feature new file mode 100644 index 0000000..f8ee384 --- /dev/null +++ b/atlas/book/gherkin-samples/en/pet-owner/02-reservar-turno.feature @@ -0,0 +1,140 @@ +# Fuente: album/book/ops-templates/pet-owner/02-reservar-turno.md +# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario +# Tests Backend: pytest tests/contracts/workflows/test_turnero_general.py +# Tests Frontend: npx playwright test turnero.spec.ts +# Relacionado: def/work_plan/10-flow-turnero.md + +Feature: Reservar turno veterinario (Turnero) + + Como dueno de mascota + Quiero reservar un turno veterinario a domicilio + Para que atiendan a mi mascota sin salir de casa + + Background: + Given que estoy en la pagina del turnero + + # ============================================ + # VERIFICACION DE COBERTURA + # ============================================ + + Scenario: Verificar cobertura en zona disponible + When ingreso direccion "Av Santa Fe 1234, CABA" + Then deberia ver mensaje "Tenemos cobertura en tu zona" + And deberia poder continuar al siguiente paso + + Scenario: Zona sin cobertura + When ingreso direccion "Calle Principal 100, Ushuaia" + Then deberia ver mensaje "Aun no tenemos cobertura en tu zona" + And deberia ver formulario "Avisame cuando lleguen" + + # ============================================ + # FLUJO COMPLETO POR TIPO DE USUARIO + # ============================================ + + Scenario Outline: Reservar turno como + Given que soy un usuario + And tengo cobertura en mi zona + When completo los datos de mi mascota: + | campo | valor | + | nombre | Luna | + | tipo | Gato | + | edad | 2 años | + | castrada | Si | + And selecciono servicios: + | servicio | + | Vacunacion | + And selecciono fechas preferidas: + | fecha | franja | + | 2024-01-15 | Mañana | + | 2024-01-16 | Tarde | + And completo datos de contacto con email "" + And envio la solicitud + Then deberia crearse una solicitud en estado "Pendiente" + And el dueno deberia ser + And deberia recibir email de confirmacion + + Examples: + | tipo_usuario | email | estado_dueno | + | invitado | nuevo@test.com | creado como invitado | + | registrado | user@test.com | mi cuenta existente | + | recurrente | conocido@test.com | identificado por email | + + # ============================================ + # SELECCION DE SERVICIOS + # ============================================ + + Scenario: Servicios filtrados por tipo de mascota + Given que agregue una mascota tipo "Gato" + When veo los servicios disponibles + Then deberia ver "Vacuna triple felina" + And deberia ver "Vacuna antirabica" + But no deberia ver "Vacuna sextuple canina" + + Scenario: Consulta clinica se agrega automaticamente con vacunacion + Given que estoy seleccionando servicios + When selecciono "Vacunacion" + Then "Consulta clinica" deberia agregarse automaticamente + And deberia ver nota "Incluye revision general" + And no deberia poder quitar "Consulta clinica" + + Scenario: Servicios combo con descuento + Given que estoy seleccionando servicios + When agrego los siguientes servicios: + | servicio | + | Vacunacion | + | Desparasitacion | + | Antipulgas | + Then deberia ver "Plan preventivo completo" + And el total deberia incluir descuento de combo + + Scenario: Castracion no disponible para mascota castrada + Given que mi mascota esta marcada como castrada + When veo los servicios disponibles + Then no deberia ver "Castracion" + + # ============================================ + # DATOS DE CONTACTO Y CUENTA + # ============================================ + + Scenario: Pre-llenado de datos para usuario logueado + Given que estoy logueado como "maria@ejemplo.com" + And tengo registrada mascota "Firulais" + When inicio el flujo de turnero + Then mi direccion deberia estar pre-llenada + And deberia poder seleccionar "Firulais" de mis mascotas + And mis datos de contacto ya deberian estar completos + + Scenario: Detectar usuario existente por email + Given que soy usuario invitado + And existe una cuenta con email "existente@ejemplo.com" + When ingreso email "existente@ejemplo.com" en datos de contacto + Then deberia ver "Ya tenes cuenta con este email" + And deberia ver opciones: + | opcion | + | Iniciar sesion | + | Continuar como invitado | + + # ============================================ + # EDGE CASES + # ============================================ + + Scenario: Usuario abandona flujo a mitad + Given que complete los datos de mascota + And cerre el navegador sin enviar + When vuelvo a la pagina del turnero + Then deberia poder recuperar mi progreso + # Nota: datos guardados en localStorage o session + + Scenario: Multiples mascotas en una solicitud + Given que quiero atender a 2 mascotas + When agrego mascota "Luna" tipo "Gato" + And agrego mascota "Rocky" tipo "Perro" + And selecciono servicios para cada una + Then deberia crearse una solicitud con 2 mascotas + And el precio deberia reflejar ambas + + Scenario: Franja horaria especifica + Given que solo puedo por la mañana + When selecciono franja "Mañana (9-12hs)" + Then la solicitud deberia registrar esa preferencia + # Nota: Es preferencia, no garantia diff --git a/atlas/book/gherkin-samples/en/pet-owner/03-gestion-mascotas.feature b/atlas/book/gherkin-samples/en/pet-owner/03-gestion-mascotas.feature new file mode 100644 index 0000000..c618e7b --- /dev/null +++ b/atlas/book/gherkin-samples/en/pet-owner/03-gestion-mascotas.feature @@ -0,0 +1,153 @@ +# Fuente: album/book/ops-templates/pet-owner/03-gestion-mascotas.md +# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario +# Tests Backend: pytest tests/contracts/mascotas/test_pets.py +# Tests Frontend: npx playwright test pets.spec.ts + +Feature: Gestion de mascotas + + Como dueno de mascota registrado + Quiero gestionar la informacion de mis mascotas + Para tenerla actualizada y acceder a su historial medico + + Background: + Given que estoy logueado como dueno de mascota + And estoy en la seccion "Mis mascotas" + + # ============================================ + # AGREGAR MASCOTA + # ============================================ + + Scenario: Agregar mascota con datos minimos + When hago click en "Agregar mascota" + And completo el formulario: + | campo | valor | + | nombre | Luna | + | tipo | Gato | + And hago click en "Guardar" + Then deberia ver "Luna" en mi lista de mascotas + And Luna deberia aparecer disponible en el turnero + + Scenario: Agregar mascota con datos completos + When hago click en "Agregar mascota" + And completo el formulario: + | campo | valor | + | nombre | Rocky | + | tipo | Perro | + | raza | Labrador | + | fecha_nacimiento | 2020-03-15 | + | peso | 25 | + | sexo | Macho | + | castrado | Si | + And subo una foto de Rocky + And hago click en "Guardar" + Then deberia ver "Rocky" con su foto en mi lista + And deberia ver badge "Castrado" + + Scenario Outline: Validacion de datos de mascota + When intento agregar mascota con igual a "" + Then deberia ver error "" + + Examples: + | campo | valor | mensaje | + | nombre | | El nombre es obligatorio | + | nombre | A | Nombre muy corto | + | tipo | | Selecciona el tipo | + | peso | -5 | El peso debe ser positivo | + | peso | 500 | Peso fuera de rango | + + # ============================================ + # EDITAR MASCOTA + # ============================================ + + Scenario: Editar peso de mascota + Given que tengo una mascota "Luna" con peso 4kg + When edito a Luna + And cambio el peso a 5kg + And guardo los cambios + Then Luna deberia mostrar peso "5 kg" + + Scenario: Marcar mascota como castrada + Given que tengo una mascota "Rocky" no castrado + When edito a Rocky + And marco "Esta castrado" + And guardo los cambios + Then Rocky deberia mostrar badge "Castrado" + And el servicio "Castracion" no deberia aparecer para Rocky en el turnero + + Scenario: Actualizar foto de mascota + Given que tengo una mascota "Luna" sin foto + When edito a Luna + And subo una nueva foto + And guardo los cambios + Then deberia ver la foto de Luna en su tarjeta + + # ============================================ + # ELIMINAR MASCOTA + # ============================================ + + Scenario: Eliminar mascota sin historial + Given que tengo una mascota "Nuevo" sin visitas + When hago click en "Eliminar" para Nuevo + And confirmo la eliminacion + Then Nuevo no deberia aparecer en mi lista + # Nota: Es soft delete + + Scenario: Eliminar mascota con historial medico + Given que tengo una mascota "Luna" con visitas anteriores + When hago click en "Eliminar" para Luna + Then deberia ver advertencia "Luna tiene historial medico" + And deberia ver "El historial se conservara pero no podras verlo" + When confirmo la eliminacion + Then Luna no deberia aparecer en mi lista + + Scenario: No puedo eliminar mascota con turno pendiente + Given que tengo una mascota "Rocky" con turno pendiente + When intento eliminar a Rocky + Then deberia ver error "Rocky tiene turnos pendientes" + And deberia ver sugerencia "Cancela los turnos primero" + + # ============================================ + # VER HISTORIAL MEDICO + # ============================================ + + Scenario: Ver historial de visitas de mascota + Given que tengo una mascota "Luna" con 3 visitas completadas + When hago click en Luna + And voy a la seccion "Historial" + Then deberia ver 3 visitas listadas + And deberian estar ordenadas por fecha descendente + + Scenario: Ver detalle de visita + Given que tengo una mascota "Luna" con visitas + When veo el historial de Luna + And hago click en la primera visita + Then deberia ver: + | campo | + | Fecha | + | Veterinario | + | Diagnostico | + | Tratamiento | + | Medicamentos | + + Scenario: Mascota sin historial + Given que tengo una mascota "Nuevo" recien agregada + When veo el perfil de Nuevo + Then la seccion "Historial" deberia estar vacia + And deberia ver mensaje "Aun no hay visitas registradas" + And deberia ver boton "Reservar primer turno" + + # ============================================ + # CASOS ESPECIALES + # ============================================ + + Scenario: Mascota heredada de cuenta invitado + Given que me registre con email "juan@test.com" + And previamente reserve turno como invitado para "Firulais" + When voy a "Mis mascotas" + Then deberia ver "Firulais" en mi lista + And deberia ver su historial de visitas previas + + Scenario: Razas filtradas por tipo + When agrego una mascota tipo "Gato" + Then las razas disponibles deberian ser razas de gato + And no deberia ver razas de perro como "Labrador" diff --git a/atlas/book/gherkin-samples/en/pet-owner/04-pago-turno.feature b/atlas/book/gherkin-samples/en/pet-owner/04-pago-turno.feature new file mode 100644 index 0000000..ee3f5e6 --- /dev/null +++ b/atlas/book/gherkin-samples/en/pet-owner/04-pago-turno.feature @@ -0,0 +1,140 @@ +# Fuente: album/book/ops-templates/pet-owner/04-pago-turno.md +# Drive: 07. Finanzas y contabilidad/Mercado Pago +# Tests Backend: pytest tests/contracts/payments/test_mercadopago.py +# Tests Frontend: npx playwright test payment.spec.ts + +Feature: Pago de turno + + Como dueno de mascota con turno coordinado + Quiero pagar mi turno online + Para confirmar la visita del veterinario + + Background: + Given que tengo un turno en estado "Coordinado" + And el turno tiene asignado veterinario "Dra. Garcia" + And la fecha asignada es "15 de enero a las 10:00" + + # ============================================ + # FLUJO DE PAGO EXITOSO + # ============================================ + + Scenario: Pagar turno con tarjeta de credito + Given que estoy en el detalle de mi turno coordinado + When hago click en "Pagar" + Then deberia ser redirigido a Mercado Pago + And deberia ver el monto correcto + + When selecciono "Tarjeta de credito" + And completo los datos de la tarjeta + And confirmo el pago + Then deberia volver a la plataforma + And deberia ver "Pago exitoso" + And el turno deberia estar en estado "Pagado" + + Scenario: Recibir confirmacion de pago + Given que complete el pago exitosamente + Then deberia recibir email de confirmacion + And el email deberia contener: + | campo | + | Fecha del turno | + | Direccion | + | Veterinario asignado | + | Monto pagado | + | Numero de operacion | + + # ============================================ + # METODOS DE PAGO + # ============================================ + + Scenario Outline: Pagar con diferentes metodos + Given que estoy en Mercado Pago + When selecciono metodo "" + And completo el pago + Then el pago deberia ser + And el estado de acreditacion deberia ser "" + + Examples: + | metodo | resultado | acreditacion | + | Tarjeta credito | exitoso | inmediata | + | Tarjeta debito | exitoso | inmediata | + | Dinero en cuenta | exitoso | inmediata | + | Transferencia | pendiente | 1-2 dias | + | Rapipago | pendiente | hasta 24hs | + + # ============================================ + # MANEJO DE ERRORES + # ============================================ + + Scenario: Pago rechazado por fondos insuficientes + Given que estoy en Mercado Pago + When intento pagar con tarjeta sin fondos + Then deberia ver error "Fondos insuficientes" + And deberia poder reintentar con otra tarjeta + And el turno deberia seguir en estado "Coordinado" + + Scenario: Usuario cancela el pago + Given que estoy en Mercado Pago + When hago click en "Volver al sitio" + Then deberia volver a la plataforma + And deberia ver mensaje "El pago fue cancelado" + And deberia ver boton "Reintentar pago" + And el turno deberia seguir en estado "Coordinado" + + Scenario: Cierre de browser durante pago + Given que estoy en Mercado Pago + And cierro el navegador accidentalmente + When vuelvo a la plataforma + And voy a "Mis turnos" + Then deberia poder ver el estado real del pago + # Si se proceso: Pagado. Si no: Coordinado con opcion de pagar + + # ============================================ + # CASOS ESPECIALES + # ============================================ + + Scenario: Link de pago expirado + Given que recibi el link de pago hace mas de 24 horas + When hago click en el link + Then deberia ver "Este link ha expirado" + And deberia ver "Contacta a soporte para generar uno nuevo" + + Scenario: Intento pagar turno ya pagado + Given que mi turno ya esta en estado "Pagado" + When accedo al link de pago + Then deberia ver "Este turno ya fue pagado" + And deberia ver boton "Ver detalle del turno" + + Scenario: Precio cambio desde la coordinacion + # Caso muy raro pero posible + Given que el precio del servicio aumento desde que se coordino + When voy a pagar + Then deberia ver el precio original acordado + # El precio se congela al momento de coordinacion + + # ============================================ + # INTEGRACION CON WEBHOOK + # ============================================ + + Rule: El estado del turno se actualiza via webhook de Mercado Pago + + Scenario: Webhook confirma pago aprobado + Given que el usuario completo el pago en Mercado Pago + When Mercado Pago envia webhook con status "approved" + Then el turno deberia cambiar a estado "Pagado" + And el veterinario deberia recibir notificacion + And el usuario deberia recibir email de confirmacion + + Scenario: Webhook informa pago pendiente + Given que el usuario pago con transferencia bancaria + When Mercado Pago envia webhook con status "pending" + Then el turno deberia quedarse en "Coordinado" + And deberia registrarse el pago pendiente + And el usuario deberia recibir email "Esperando acreditacion" + + Scenario: Webhook falla pero pago se proceso + # Caso de error que requiere intervencion manual + Given que el usuario pago exitosamente + But el webhook fallo por error de red + Then el turno seguira en "Coordinado" + And el equipo de ops deberia recibir alerta + And deberian poder actualizar manualmente diff --git a/atlas/book/gherkin-samples/en/pet-owner/05-historial-medico.feature b/atlas/book/gherkin-samples/en/pet-owner/05-historial-medico.feature new file mode 100644 index 0000000..837447a --- /dev/null +++ b/atlas/book/gherkin-samples/en/pet-owner/05-historial-medico.feature @@ -0,0 +1,147 @@ +# Fuente: album/book/ops-templates/pet-owner/05-historial-medico.md +# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario +# Tests Backend: pytest tests/contracts/mascotas/test_vet_visits.py +# Tests Frontend: npx playwright test medical-history.spec.ts + +Feature: Ver historial medico + + Como dueno de mascota + Quiero ver el historial medico de mis mascotas + Para tener registro de sus visitas y tratamientos + + Background: + Given que estoy logueado como dueno de mascota + And tengo una mascota "Luna" con visitas completadas + + # ============================================ + # ACCESO AL HISTORIAL + # ============================================ + + Scenario: Acceder al historial desde mascotas + Given que estoy en "Mis mascotas" + When hago click en "Luna" + And voy a la seccion "Historial de visitas" + Then deberia ver lista de visitas de Luna + + Scenario: Acceder al historial desde turnos + Given que estoy en "Mis turnos" + And filtro por "Completados" + When hago click en un turno de Luna + And hago click en "Ver informe medico" + Then deberia ver el informe de esa visita + + # ============================================ + # VISUALIZACION DE VISITAS + # ============================================ + + Scenario: Ver lista de visitas ordenadas + Given que Luna tiene visitas en las siguientes fechas: + | fecha | + | 2024-01-15 | + | 2023-11-20 | + | 2023-06-10 | + When veo el historial de Luna + Then deberia ver las visitas ordenadas de mas reciente a mas antigua + And la primera deberia ser del "15 de enero 2024" + + Scenario: Ver detalle completo de informe + When hago click en una visita completada + Then deberia ver los siguientes datos: + | seccion | contenido | + | Informacion | Fecha, veterinario, servicios | + | Examen fisico | Peso, temperatura, FC, obs | + | Diagnostico | Descripcion del diagnostico | + | Tratamiento | Plan de tratamiento | + | Medicamentos | Lista con dosis y frecuencia | + | Estudios | Estudios solicitados y estado | + | Seguimiento | Recomendaciones, proximo control | + + Scenario: Ver informe con medicamentos recetados + Given que la visita incluye medicamentos: + """ + 1. Amoxicilina 250mg - 1 comprimido cada 12hs por 7 dias + 2. Meloxicam 1.5mg - 1 comprimido por dia por 3 dias + """ + When veo el informe + Then deberia ver la lista de medicamentos + And cada medicamento deberia mostrar: + | campo | + | Nombre | + | Dosis | + | Frecuencia | + | Duracion | + + # ============================================ + # DESCARGA Y COMPARTIR + # ============================================ + + Scenario: Descargar informe como PDF + Given que estoy viendo un informe medico + When hago click en "Descargar PDF" + Then deberia descargarse un archivo PDF + And el PDF deberia contener toda la informacion del informe + And deberia tener el logo de Amar Mascotas + + Scenario: Imprimir informe + Given que estoy viendo un informe medico + When hago click en "Imprimir" + Then deberia abrirse el dialogo de impresion + And el formato deberia ser optimizado para impresion + + # ============================================ + # CASOS ESPECIALES + # ============================================ + + Scenario: Visita completada sin informe cargado + # El vet no cargo el informe todavia + Given que Luna tiene una visita marcada como "Completada" + But el veterinario no cargo el informe aun + When intento ver el informe + Then deberia ver mensaje "Informe pendiente" + And deberia ver "El veterinario esta completando el informe" + + Scenario: Informe con estudios pendientes + Given que el informe solicita estudios de laboratorio + And los resultados aun no estan disponibles + When veo el informe + Then deberia ver seccion "Estudios solicitados" + And deberia ver estado "Pendiente de resultados" + And deberia ver nota "Se notificara cuando esten listos" + + Scenario: Mascota con multiples visitas el mismo dia + # Raro pero posible: consulta de urgencia + seguimiento + Given que Luna tuvo 2 visitas el 15 de enero + When veo el historial + Then deberia ver ambas visitas listadas + And deberian estar diferenciadas por hora + + # ============================================ + # PERMISOS Y PRIVACIDAD + # ============================================ + + Rule: Solo el dueno puede ver el historial de su mascota + + Scenario: Dueno ve historial de su mascota + Given que soy dueno de Luna + When accedo al historial de Luna + Then deberia poder verlo completo + + Scenario: No puedo ver historial de mascota ajena + Given que existe mascota "Rocky" de otro dueno + When intento acceder al historial de Rocky + Then deberia ver error "No tienes acceso" + # 403 Forbidden + + # ============================================ + # BUSQUEDA Y FILTROS + # ============================================ + + Scenario: Filtrar historial por tipo de servicio + Given que Luna tiene visitas de vacunacion y consulta + When filtro por "Vacunacion" + Then solo deberia ver visitas de vacunacion + + Scenario: Buscar en historial por fecha + When busco visitas del "2023" + Then solo deberia ver visitas del 2023 + And no deberia ver visitas del 2024 diff --git a/atlas/book/gherkin-samples/en/veterinarian/01-aceptar-solicitud.feature b/atlas/book/gherkin-samples/en/veterinarian/01-aceptar-solicitud.feature new file mode 100644 index 0000000..d708ef7 --- /dev/null +++ b/atlas/book/gherkin-samples/en/veterinarian/01-aceptar-solicitud.feature @@ -0,0 +1,109 @@ +# Fuente: album/book/ops-templates/veterinarian/01-aceptar-solicitud.md +# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario +# Tests Backend: pytest tests/contracts/solicitudes/test_service_requests.py +# Tests Frontend: npx playwright test vet-requests.spec.ts + +Feature: Aceptar o rechazar solicitudes de servicio + + Como veterinario de la plataforma + Quiero revisar y responder a solicitudes en mi zona + Para gestionar mi agenda de visitas a domicilio + + Background: + Given que estoy logueado como veterinario + And tengo cobertura en los barrios "Palermo" y "Recoleta" + And estoy en el dashboard de veterinario + + # ============================================ + # VER SOLICITUDES PENDIENTES + # ============================================ + + Scenario: Ver lista de solicitudes en mi zona + When veo la seccion "Solicitudes pendientes" + Then deberia ver solo solicitudes de "Palermo" y "Recoleta" + And no deberia ver solicitudes de otros barrios + + Scenario: Ver detalle de solicitud + Given que hay una solicitud pendiente + When hago click en la solicitud + Then deberia ver: + | seccion | contenido | + | Dueno | Nombre, telefono, direccion | + | Mascota | Nombre, tipo, edad, foto | + | Servicios | Lista de servicios solicitados | + | Fechas | Fechas preferidas por el dueno | + | Historial | Visitas anteriores si las hay | + + # ============================================ + # ACEPTAR SOLICITUD + # ============================================ + + Scenario: Aceptar solicitud con fecha disponible + Given que hay una solicitud para el barrio "Palermo" + And el dueno prefiere fechas: + | fecha | franja | + | 2024-01-15 | Mañana | + | 2024-01-16 | Tarde | + And tengo disponibilidad el 15 de enero a las 10:00 + When hago click en "Aceptar" + And selecciono fecha "15 de enero" hora "10:00" + And confirmo la aceptacion + Then la solicitud deberia pasar a estado "Coordinado" + And deberia quedar asignada a mi + And el dueno deberia recibir notificacion con mis datos + And la visita deberia aparecer en mi agenda + + Scenario: Aceptar solicitud con datos de mascota que ya atendi + Given que hay una solicitud para mascota "Luna" + And yo atendi a "Luna" anteriormente + When veo el detalle de la solicitud + Then deberia ver badge "Paciente recurrente" + And deberia ver el historial de mis visitas anteriores a Luna + + # ============================================ + # RECHAZAR SOLICITUD + # ============================================ + + Scenario Outline: Rechazar solicitud con motivo + Given que hay una solicitud pendiente + When hago click en "Rechazar" + And selecciono motivo "" + And confirmo el rechazo + Then la solicitud deberia desaparecer de mi lista + And deberia seguir visible para otros veterinarios + + Examples: + | motivo | + | No tengo disponibilidad | + | Fuera de mi zona | + | No realizo este servicio | + | Otro | + + # ============================================ + # RACE CONDITIONS + # ============================================ + + Rule: Solo un veterinario puede aceptar cada solicitud + + Scenario: Otro vet acepta mientras estoy viendo + Given que estoy viendo el detalle de una solicitud + And otro veterinario acepta la misma solicitud + When intento aceptarla + Then deberia ver error "Esta solicitud ya fue aceptada" + And deberia ser redirigido al listado + + # ============================================ + # CASOS ESPECIALES + # ============================================ + + Scenario: Solicitud urgente destacada + Given que hay una solicitud marcada como "Urgente" + When veo el listado de solicitudes + Then deberia ver la solicitud con indicador de urgencia + And deberia aparecer primero en la lista + + Scenario: No puedo aceptar con agenda completa + Given que tengo mi agenda completa para las fechas de la solicitud + When intento aceptar la solicitud + Then deberia ver advertencia "No tienes disponibilidad en las fechas preferidas" + And deberia poder proponer una fecha alternativa diff --git a/atlas/book/gherkin-samples/en/veterinarian/02-gestion-agenda.feature b/atlas/book/gherkin-samples/en/veterinarian/02-gestion-agenda.feature new file mode 100644 index 0000000..7ef5b94 --- /dev/null +++ b/atlas/book/gherkin-samples/en/veterinarian/02-gestion-agenda.feature @@ -0,0 +1,99 @@ +# Fuente: album/book/ops-templates/veterinarian/02-gestion-agenda.md +# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario +# Tests Backend: pytest tests/contracts/mascotas/test_vet_availability.py +# Tests Frontend: npx playwright test vet-schedule.spec.ts + +Feature: Gestion de agenda veterinaria + + Como veterinario + Quiero gestionar mi calendario y disponibilidad + Para organizar mis visitas a domicilio eficientemente + + Background: + Given que estoy logueado como veterinario + And estoy en la seccion "Mi agenda" + + # ============================================ + # VER AGENDA + # ============================================ + + Scenario: Ver calendario semanal + When veo mi agenda en vista semanal + Then deberia ver los 7 dias de la semana + And deberia ver mis visitas programadas + And cada visita deberia mostrar hora y nombre del paciente + + Scenario: Ver visitas con codigo de color por estado + Given que tengo visitas en diferentes estados + When veo mi agenda + Then las visitas deberian mostrarse con colores: + | estado | color | + | Coordinado | amarillo | + | Pagado | verde | + | En progreso | azul | + | Completado | gris | + + # ============================================ + # CONFIGURAR DISPONIBILIDAD SEMANAL + # ============================================ + + Scenario: Configurar horario laboral + When voy a "Configuracion" -> "Mi disponibilidad" + And configuro mi horario: + | dia | trabajo | desde | hasta | pausa_desde | pausa_hasta | + | Lunes | Si | 09:00 | 18:00 | 13:00 | 14:00 | + | Martes | Si | 09:00 | 18:00 | 13:00 | 14:00 | + | Miercoles | Si | 09:00 | 18:00 | 13:00 | 14:00 | + | Jueves | Si | 09:00 | 18:00 | 13:00 | 14:00 | + | Viernes | Si | 09:00 | 15:00 | | | + | Sabado | No | | | | | + | Domingo | No | | | | | + And guardo la configuracion + Then mi disponibilidad deberia actualizarse + And no deberia recibir solicitudes fuera de ese horario + + # ============================================ + # BLOQUEAR DIAS ESPECIFICOS + # ============================================ + + Scenario: Marcar dia como no disponible + Given que necesito el 20 de enero libre + When hago click en el dia 20 de enero en el calendario + And selecciono "Marcar como no disponible" + And ingreso motivo "Vacaciones" + And confirmo + Then el dia 20 deberia mostrarse como bloqueado + And no deberia poder aceptar solicitudes para ese dia + + Scenario: Bloquear rango de fechas + Given que tomo vacaciones del 15 al 22 de enero + When voy a "Agregar indisponibilidad" + And selecciono fecha inicio "15 de enero" + And selecciono fecha fin "22 de enero" + And ingreso motivo "Vacaciones" + And confirmo + Then todos esos dias deberian mostrarse como bloqueados + + # ============================================ + # IMPACTO EN SOLICITUDES + # ============================================ + + Rule: Los bloqueos no afectan visitas ya aceptadas + + Scenario: Bloquear dia con visita ya aceptada + Given que tengo una visita aceptada para el 15 de enero + When intento bloquear el 15 de enero + Then deberia ver advertencia "Tienes una visita programada ese dia" + And deberia poder elegir: + | opcion | + | Cancelar el bloqueo | + | Bloquear y contactar al dueno | + + Rule: Cambios de disponibilidad solo afectan solicitudes futuras + + Scenario: Cambiar horario no afecta visitas existentes + Given que tengo una visita a las 17:00 el lunes + When cambio mi horario del lunes para terminar a las 16:00 + And guardo los cambios + Then la visita de las 17:00 deberia mantenerse + And deberia ver advertencia sobre la inconsistencia diff --git a/atlas/book/gherkin-samples/en/veterinarian/03-realizar-visita.feature b/atlas/book/gherkin-samples/en/veterinarian/03-realizar-visita.feature new file mode 100644 index 0000000..cc6dbd5 --- /dev/null +++ b/atlas/book/gherkin-samples/en/veterinarian/03-realizar-visita.feature @@ -0,0 +1,127 @@ +# Fuente: album/book/ops-templates/veterinarian/03-realizar-visita.md +# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario +# Tests Backend: pytest tests/contracts/mascotas/test_vet_visits.py +# Tests Frontend: npx playwright test vet-visit.spec.ts + +Feature: Realizar visita y crear informe medico + + Como veterinario + Quiero registrar la atencion medica de mis visitas + Para mantener el historial clinico del paciente + + Background: + Given que estoy logueado como veterinario + And tengo una visita programada para hoy con mascota "Luna" + And la visita esta en estado "Pagado" + + # ============================================ + # INICIAR VISITA + # ============================================ + + Scenario: Ver informacion antes de la visita + When accedo al detalle de la visita + Then deberia ver la direccion para llegar + And deberia ver el telefono del dueno para confirmar + And deberia ver los servicios a realizar + And deberia ver el historial previo de Luna + + Scenario: Iniciar visita al llegar + Given que llegue al domicilio + When hago click en "Iniciar visita" + Then el estado deberia cambiar a "En progreso" + And deberia registrarse la hora de inicio + And deberia habilitarse el boton "Crear informe" + + # ============================================ + # CREAR INFORME MEDICO + # ============================================ + + Scenario: Completar informe con examen fisico + Given que la visita esta en progreso + When voy a "Crear informe" + And completo el examen fisico: + | campo | valor | + | Peso | 4.5 kg | + | Temperatura | 38.5 °C | + | Frecuencia cardiaca | 120 lpm | + | Frecuencia resp | 25 rpm | + | Mucosas | Rosadas | + | Hidratacion | Normal | + And guardo el informe + Then el examen fisico deberia guardarse + + Scenario: Agregar diagnostico y tratamiento + Given que complete el examen fisico + When agrego el diagnostico: + """ + Otitis externa bilateral leve. + Paciente presenta prurito y secrecion ceruminosa. + Sin signos de infeccion secundaria. + """ + And agrego el tratamiento: + """ + Limpieza de oidos con solucion fisiologica. + Aplicacion de gotas oticas antibioticas. + Control en 7 dias. + """ + And guardo el informe + Then el diagnostico y tratamiento deberian guardarse + + Scenario: Recetar medicamentos + Given que estoy creando el informe + When agrego medicamentos: + | nombre | dosis | frecuencia | duracion | + | Otomax gotas | 5 gotas | cada 12 horas | 7 dias | + | Meloxicam 1.5mg | 1 comp | cada 24 horas | 3 dias | + And guardo el informe + Then los medicamentos deberian aparecer en el informe + And el dueno podra verlos desde su cuenta + + # ============================================ + # COMPLETAR VISITA + # ============================================ + + Scenario: Completar visita con informe + Given que el informe esta completo + When hago click en "Completar visita" + And confirmo la finalizacion + Then la visita deberia pasar a estado "Completado" + And deberia generarse factura electronica (AFIP) + And el dueno deberia recibir notificacion + And el informe deberia ser visible para el dueno + + Scenario: No puedo completar sin informe + Given que la visita esta en progreso + But no cree ningun informe + When intento completar la visita + Then deberia ver error "Debes crear el informe antes de completar" + + # ============================================ + # GUARDADO AUTOMATICO + # ============================================ + + Rule: El informe se guarda automaticamente + + Scenario: Guardado automatico cada 30 segundos + Given que estoy escribiendo el informe + When pasan 30 segundos + Then deberia ver indicador "Guardado automaticamente" + + Scenario: Recuperar informe despues de desconexion + Given que estaba escribiendo el informe + And perdi conexion a internet + When recupero la conexion + Then deberia recuperar mi progreso + And no deberia perder lo que escribi + + # ============================================ + # CASOS ESPECIALES + # ============================================ + + Scenario: Cancelar visita in situ + Given que llegue al domicilio + But el dueno no esta + When marco la visita como "Cancelada in situ" + And selecciono motivo "Dueno ausente" + Then la visita deberia marcarse como cancelada + And deberia generarse cargo por visita fallida diff --git a/atlas/book/gherkin-samples/en/veterinarian/04-zonas-cobertura.feature b/atlas/book/gherkin-samples/en/veterinarian/04-zonas-cobertura.feature new file mode 100644 index 0000000..16df339 --- /dev/null +++ b/atlas/book/gherkin-samples/en/veterinarian/04-zonas-cobertura.feature @@ -0,0 +1,83 @@ +# Fuente: album/book/ops-templates/veterinarian/04-zonas-cobertura.md +# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario +# Tests Backend: pytest tests/contracts/mascotas/test_veterinarians.py +# Tests Frontend: npx playwright test vet-coverage.spec.ts + +Feature: Gestionar zonas de cobertura + + Como veterinario + Quiero definir en que zonas atiendo + Para recibir solo solicitudes que puedo cubrir + + Background: + Given que estoy logueado como veterinario + And estoy en la seccion "Mi cobertura" + + # ============================================ + # VER ZONAS ACTUALES + # ============================================ + + Scenario: Ver mapa con mis zonas de cobertura + When cargo la pagina de cobertura + Then deberia ver un mapa de la ciudad + And mis zonas cubiertas deberian estar resaltadas + And deberia ver un listado de barrios seleccionados + + Scenario: Ver estadisticas por zona + Given que tengo cobertura en "Palermo" y "Recoleta" + When veo el detalle de mis zonas + Then deberia ver para cada zona: + | metrica | + | Solicitudes este mes | + | Visitas completadas | + | Otros vets en la zona | + + # ============================================ + # AGREGAR ZONAS + # ============================================ + + Scenario: Agregar barrio desde el mapa + Given que no tengo cobertura en "Belgrano" + When hago click en "Belgrano" en el mapa + And confirmo agregar la zona + Then "Belgrano" deberia aparecer en mi lista de zonas + And deberia empezar a ver solicitudes de Belgrano + + Scenario: Agregar multiples zonas a la vez + When selecciono los barrios: + | barrio | + | Colegiales | + | Chacarita | + | Villa Crespo | + And hago click en "Agregar seleccionados" + Then los 3 barrios deberian agregarse a mi cobertura + + # ============================================ + # QUITAR ZONAS + # ============================================ + + Scenario: Quitar zona de cobertura + Given que tengo cobertura en "Recoleta" + When hago click en "X" junto a "Recoleta" + And confirmo quitar la zona + Then "Recoleta" no deberia estar en mi lista + And no deberia ver nuevas solicitudes de Recoleta + + # ============================================ + # IMPACTO EN SOLICITUDES + # ============================================ + + Rule: Los cambios de zona afectan solo solicitudes nuevas + + Scenario: Agregar zona muestra solicitudes existentes + Given que no tengo cobertura en "Belgrano" + And hay 3 solicitudes pendientes en Belgrano + When agrego "Belgrano" a mi cobertura + Then deberia ver las 3 solicitudes pendientes de Belgrano + + Scenario: Quitar zona no afecta visitas aceptadas + Given que tengo cobertura en "Recoleta" + And tengo una visita aceptada en Recoleta para mañana + When quito "Recoleta" de mi cobertura + Then la visita de mañana deberia mantenerse + But no deberia ver nuevas solicitudes de Recoleta diff --git a/atlas/book/gherkin-samples/en/veterinarian/05-historial-pacientes.feature b/atlas/book/gherkin-samples/en/veterinarian/05-historial-pacientes.feature new file mode 100644 index 0000000..c5bcf9e --- /dev/null +++ b/atlas/book/gherkin-samples/en/veterinarian/05-historial-pacientes.feature @@ -0,0 +1,100 @@ +# Fuente: album/book/ops-templates/veterinarian/05-historial-pacientes.md +# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario +# Tests Backend: pytest tests/contracts/mascotas/test_vet_visits.py +# Tests Frontend: npx playwright test vet-history.spec.ts + +Feature: Ver historial de pacientes + + Como veterinario + Quiero acceder al historial medico de pacientes + Para tener contexto clinico en mis atenciones + + Background: + Given que estoy logueado como veterinario + And estoy en la seccion "Historia clinica" + + # ============================================ + # BUSCAR PACIENTES + # ============================================ + + Scenario Outline: Buscar paciente por diferentes criterios + When busco por con valor "" + Then deberia ver resultados que coincidan + + Examples: + | criterio | valor | + | nombre dueno | Maria Garcia | + | nombre mascota | Luna | + | telefono | 1155551234 | + | email | maria@ejemplo.com | + + Scenario: Busqueda sin resultados + When busco "ZZZZZ paciente inexistente" + Then deberia ver mensaje "No se encontraron resultados" + And deberia ver sugerencia "Verifica la ortografia" + + # ============================================ + # VER FICHA DE MASCOTA + # ============================================ + + Scenario: Ver ficha completa de mascota + Given que encontre a la mascota "Luna" + When hago click en Luna + Then deberia ver la ficha con: + | seccion | contenido | + | Datos basicos | Nombre, tipo, raza, edad, peso | + | Foto | Foto de la mascota | + | Dueno | Nombre y contacto del dueno | + | Vacunacion | Estado de vacunas | + | Historial | Lista de visitas | + + # ============================================ + # VER HISTORIAL DE VISITAS + # ============================================ + + Scenario: Ver listado de visitas + Given que estoy viendo la ficha de "Luna" + And Luna tiene 5 visitas completadas + When veo la seccion "Historial de visitas" + Then deberia ver las 5 visitas listadas + And deberian estar ordenadas de mas reciente a mas antigua + + Scenario: Ver informe de visita de otro veterinario + Given que Luna fue atendida por "Dra. Rodriguez" + And yo no la atendi en esa visita + When hago click en esa visita + Then deberia poder ver el informe completo + # Para continuidad de atencion + + # ============================================ + # FILTROS Y NAVEGACION + # ============================================ + + Scenario: Filtrar historial por tipo de servicio + Given que estoy viendo el historial de "Luna" + When filtro por servicio "Vacunacion" + Then solo deberia ver visitas de vacunacion + + Scenario: Filtrar por mis atenciones + Given que estoy viendo el historial de "Luna" + And Luna fue atendida por varios veterinarios + When marco "Solo mis atenciones" + Then solo deberia ver las visitas que yo realice + + # ============================================ + # PERMISOS + # ============================================ + + Rule: Veterinarios pueden ver historial de pacientes que atendieron + + Scenario: Puedo ver historial de paciente que atendi + Given que yo atendi a "Luna" al menos una vez + When busco a Luna + Then deberia poder ver su historial completo + + Scenario: Puedo ver historial de paciente con solicitud pendiente + Given que hay una solicitud pendiente para "Rocky" + And la solicitud esta en mi zona + When busco a Rocky + Then deberia poder ver su historial + # Para evaluar si acepto la solicitud diff --git a/atlas/book/gherkin-samples/es/backoffice/01-gestion-solicitudes.feature b/atlas/book/gherkin-samples/es/backoffice/01-gestion-solicitudes.feature new file mode 100644 index 0000000..042da52 --- /dev/null +++ b/atlas/book/gherkin-samples/es/backoffice/01-gestion-solicitudes.feature @@ -0,0 +1,158 @@ +# language: es +# Fuente: album/book/ops-templates/backoffice/01-gestion-solicitudes.md +# Drive: 05. ATC - Operaciones/Procedimientos +# Tests Backend: pytest tests/contracts/solicitudes/test_service_requests.py +# Tests Frontend: npx playwright test admin-requests.spec.ts + +Característica: Gestion de solicitudes de servicio + + Como administrador del backoffice + Quiero gestionar todas las solicitudes del sistema + Para asegurar que se atiendan correctamente + + Antecedentes: + Dado que estoy logueado como administrador + Y estoy en la seccion "Solicitudes" del backoffice + + # ============================================ + # VER Y FILTRAR SOLICITUDES + # ============================================ + + Escenario: Ver listado de todas las solicitudes + Cuando cargo la pagina de solicitudes + Entonces deberia ver un listado paginado + Y cada solicitud deberia mostrar: + | campo | + | ID | + | Dueno | + | Mascota | + | Servicios | + | Estado | + | Fecha | + + Esquema del escenario: Filtrar solicitudes por estado + Cuando filtro por estado "" + Entonces solo deberia ver solicitudes con estado "" + + Ejemplos: + | estado | + | Pendiente | + | Coordinado | + | Pagado | + | Completado | + | Cancelado | + + Escenario: Buscar solicitud especifica + Cuando busco por email "maria@ejemplo.com" + Entonces deberia ver solo solicitudes de ese dueno + + Escenario: Filtrar por rango de fechas + Cuando filtro desde "01/01/2024" hasta "31/01/2024" + Entonces solo deberia ver solicitudes de enero 2024 + + # ============================================ + # ASIGNAR VETERINARIO MANUALMENTE + # ============================================ + + Escenario: Asignar veterinario a solicitud pendiente + Dado que hay una solicitud pendiente en "Palermo" + Y no fue aceptada por ningun veterinario + Cuando abro el detalle de la solicitud + Y hago click en "Asignar veterinario" + Entonces deberia ver lista de veterinarios con cobertura en Palermo + + Cuando selecciono "Dra. Garcia" + Y selecciono fecha "15 de enero" hora "10:00" + Y confirmo la asignacion + Entonces la solicitud deberia pasar a estado "Coordinado" + Y deberia estar asignada a Dra. Garcia + Y el dueno deberia recibir notificacion + Y el veterinario deberia recibir notificacion + + Escenario: Ver disponibilidad de veterinarios antes de asignar + Dado que estoy asignando un veterinario + Cuando veo la lista de veterinarios disponibles + Entonces deberia ver para cada uno: + | informacion | + | Nombre | + | Disponibilidad | + | Visitas del dia | + | Distancia a destino | + + # ============================================ + # CAMBIAR ESTADO MANUALMENTE + # ============================================ + + Escenario: Cambiar estado de solicitud + Dado que hay una solicitud en estado "Coordinado" + Y el pago se proceso pero el webhook fallo + Cuando abro la solicitud + Y hago click en "Cambiar estado" + Y selecciono "Pagado" + Y ingreso motivo "Pago confirmado manualmente - ID MP: 12345" + Y confirmo el cambio + Entonces la solicitud deberia pasar a "Pagado" + Y el cambio deberia registrarse en el historial + Y deberia quedar mi usuario como responsable del cambio + + Regla: Solo ciertos cambios de estado son validos + + Escenario: No puedo volver a estado anterior + Dado que hay una solicitud en estado "Completado" + Cuando intento cambiar el estado a "Pagado" + Entonces deberia ver error "No se puede volver a un estado anterior" + + Escenario: Puedo cancelar desde cualquier estado + Dado que hay una solicitud en estado "Coordinado" + Cuando cambio el estado a "Cancelado" + Y ingreso motivo de cancelacion + Entonces la solicitud deberia cancelarse + + # ============================================ + # VER DETALLE COMPLETO + # ============================================ + + Escenario: Ver historial de cambios de una solicitud + Dado que hay una solicitud con varios cambios de estado + Cuando abro el detalle de la solicitud + Y voy a la pestaña "Historial" + Entonces deberia ver todos los cambios con: + | campo | + | Fecha y hora | + | Estado anterior | + | Estado nuevo | + | Usuario | + | Motivo | + + Escenario: Ver informacion de pago + Dado que hay una solicitud pagada + Cuando abro el detalle + Entonces deberia ver seccion "Pago" con: + | campo | + | Monto | + | Fecha de pago | + | Metodo | + | ID operacion MP | + | Estado del pago | + + # ============================================ + # CASOS ESPECIALES + # ============================================ + + Escenario: Reasignar veterinario + Dado que hay una solicitud asignada a "Dr. Lopez" + Y Dr. Lopez no puede asistir + Cuando abro la solicitud + Y hago click en "Reasignar" + Y selecciono otro veterinario + Y confirmo + Entonces Dr. Lopez deberia ser notificado de la desasignacion + Y el nuevo vet deberia ser notificado + Y el dueno deberia ser notificado del cambio + + Escenario: Solicitud sin veterinarios disponibles + Dado que hay una solicitud pendiente en una zona remota + Y no hay veterinarios con cobertura en esa zona + Cuando intento asignar veterinario + Entonces deberia ver mensaje "No hay veterinarios disponibles" + Y deberia poder expandir la busqueda a zonas cercanas diff --git a/atlas/book/gherkin-samples/es/backoffice/02-gestion-usuarios.feature b/atlas/book/gherkin-samples/es/backoffice/02-gestion-usuarios.feature new file mode 100644 index 0000000..b82fb36 --- /dev/null +++ b/atlas/book/gherkin-samples/es/backoffice/02-gestion-usuarios.feature @@ -0,0 +1,131 @@ +# language: es +# Fuente: album/book/ops-templates/backoffice/02-gestion-usuarios.md +# Drive: 05. ATC - Operaciones/Procedimientos +# Tests Backend: pytest tests/contracts/mascotas/test_pet_owners.py +# Tests Frontend: npx playwright test admin-users.spec.ts + +Característica: Gestion de usuarios + + Como administrador del backoffice + Quiero gestionar duenos y veterinarios + Para mantener la base de usuarios actualizada + + Antecedentes: + Dado que estoy logueado como administrador + Y estoy en el backoffice + + # ============================================ + # GESTION DE DUENOS + # ============================================ + + Escenario: Buscar dueno de mascota + Dado que estoy en la seccion "Duenos" + Cuando busco "maria@ejemplo.com" + Entonces deberia ver a Maria Garcia en los resultados + Y deberia ver sus mascotas listadas + Y deberia ver cantidad de turnos + + Escenario: Ver perfil completo de dueno + Dado que encontre a "Maria Garcia" + Cuando hago click en su perfil + Entonces deberia ver: + | seccion | contenido | + | Datos personales | Nombre, email, telefono | + | Direcciones | Direcciones registradas | + | Mascotas | Lista de mascotas | + | Historial | Solicitudes anteriores | + | Pagos | Historial de pagos | + + Escenario: Editar datos de dueno + Dado que estoy viendo el perfil de un dueno + Cuando hago click en "Editar" + Y cambio el telefono a "1155559999" + Y guardo los cambios + Entonces el telefono deberia actualizarse + Y deberia registrarse quien hizo el cambio + + Esquema del escenario: Filtrar duenos por tipo + Dado que estoy en la lista de duenos + Cuando filtro por tipo "" + Entonces solo deberia ver usuarios + + Ejemplos: + | tipo | + | Registrados | + | Invitados | + + # ============================================ + # GESTION DE VETERINARIOS + # ============================================ + + Escenario: Ver lista de veterinarios + Cuando voy a la seccion "Veterinarios" + Entonces deberia ver listado de todos los vets + Y cada vet deberia mostrar: + | campo | + | Nombre | + | Matricula | + | Zonas | + | Estado | + | Visitas mes | + + Escenario: Agregar nuevo veterinario + Dado que estoy en la seccion "Veterinarios" + Cuando hago click en "Agregar veterinario" + Y completo los datos: + | campo | valor | + | Nombre | Dr. Juan Perez | + | Matricula | MV-12345 | + | Email | jperez@ejemplo.com | + | Telefono | 1155551234 | + | Especialidades | Clinica general | + Y selecciono zonas de cobertura: + | zona | + | Palermo | + | Recoleta | + Y genero credenciales de acceso + Y guardo + Entonces deberia crearse el veterinario + Y deberia poder loguearse con sus credenciales + + Escenario: Desactivar veterinario sin citas pendientes + Dado que el veterinario "Dr. Lopez" no tiene citas pendientes + Cuando abro su perfil + Y hago click en "Desactivar" + Y confirmo la desactivacion + Entonces Dr. Lopez deberia estar inactivo + Y no deberia recibir nuevas solicitudes + Y no deberia poder loguearse + + Escenario: Intentar desactivar veterinario con citas pendientes + Dado que el veterinario "Dra. Garcia" tiene 3 citas pendientes + Cuando intento desactivarla + Entonces deberia ver advertencia "Tiene 3 citas pendientes" + Y deberia ver opciones: + | opcion | + | Reasignar citas y desactivar | + | Cancelar | + + # ============================================ + # CASOS ESPECIALES + # ============================================ + + Escenario: Fusionar cuentas duplicadas + Dado que hay dos cuentas para el mismo dueno: + | cuenta | email | mascotas | + | Cuenta1 | maria@gmail.com | Luna | + | Cuenta2 | maria@hotmail.com | Rocky | + Cuando selecciono ambas cuentas + Y hago click en "Fusionar" + Y elijo Cuenta1 como principal + Y confirmo + Entonces deberia existir solo Cuenta1 + Y deberia tener ambas mascotas + Y el historial deberia combinarse + + Escenario: Convertir invitado a registrado + Dado que hay un usuario invitado con email "invitado@test.com" + Cuando abro su perfil + Y hago click en "Enviar invitacion a registrarse" + Entonces deberia enviarse email con link de registro + Y el usuario podra crear contraseña y activar cuenta diff --git a/atlas/book/gherkin-samples/es/backoffice/03-gestion-servicios.feature b/atlas/book/gherkin-samples/es/backoffice/03-gestion-servicios.feature new file mode 100644 index 0000000..cac9c3d --- /dev/null +++ b/atlas/book/gherkin-samples/es/backoffice/03-gestion-servicios.feature @@ -0,0 +1,131 @@ +# language: es +# Fuente: album/book/ops-templates/backoffice/03-gestion-servicios.md +# Drive: 08. IT y Producto/Catalogo Servicios +# Tests Backend: pytest tests/contracts/productos/test_services.py +# Tests Frontend: npx playwright test admin-services.spec.ts + +Característica: Gestion de servicios y precios + + Como administrador + Quiero gestionar el catalogo de servicios + Para mantener la oferta actualizada + + Antecedentes: + Dado que estoy logueado como administrador + Y estoy en la seccion "Servicios" + + # ============================================ + # VER CATALOGO DE SERVICIOS + # ============================================ + + Escenario: Ver listado de servicios + Cuando cargo la pagina de servicios + Entonces deberia ver todos los servicios organizados por categoria + Y cada servicio deberia mostrar: + | campo | + | Nombre | + | Categoria | + | Tipo mascota | + | Precio actual | + | Estado | + + Esquema del escenario: Filtrar servicios + Cuando filtro por "" + Entonces solo deberia ver servicios que coincidan + + Ejemplos: + | filtro | valor | + | categoria | Vacunacion | + | tipo_mascota | Gato | + | estado | Activo | + + # ============================================ + # CREAR Y EDITAR SERVICIOS + # ============================================ + + Escenario: Agregar nuevo servicio + Cuando hago click en "Agregar servicio" + Y completo los datos: + | campo | valor | + | Nombre | Vacuna Quintuple Felina | + | Descripcion | Protege contra 5 enfermedades | + | Categoria | Vacunacion | + | Tipo mascota | Gato | + | Precio | 15000 | + | Duracion | 30 minutos | + Y guardo el servicio + Entonces el servicio deberia crearse + Y deberia aparecer en el turnero para gatos + + Escenario: Editar servicio existente + Dado que existe el servicio "Consulta clinica" + Cuando abro el servicio + Y cambio la descripcion + Y guardo + Entonces la descripcion deberia actualizarse + Y las solicitudes existentes no deberian afectarse + + Escenario: Desactivar servicio + Dado que existe el servicio "Servicio Antiguo" + Cuando abro el servicio + Y hago click en "Desactivar" + Y confirmo + Entonces el servicio no deberia aparecer en el turnero + Y las solicitudes existentes deberian mantenerse + + # ============================================ + # GESTION DE PRECIOS + # ============================================ + + Escenario: Actualizar precio de servicio + Dado que "Consulta clinica" tiene precio actual de 10000 + Cuando abro el servicio + Y voy a la seccion "Precios" + Y hago click en "Agregar precio" + Y ingreso nuevo precio 12000 + Y selecciono fecha de vigencia "01/02/2024" + Y guardo + Entonces deberia crearse el nuevo precio + Y el precio anterior deberia quedar en historial + Y el nuevo precio deberia aplicar desde la fecha indicada + + Escenario: Ver historial de precios + Dado que un servicio tuvo varios cambios de precio + Cuando veo la seccion "Precios" + Entonces deberia ver historial con: + | campo | + | Precio | + | Fecha desde | + | Fecha hasta | + | Usuario | + + Regla: El precio se congela al crear la solicitud + + Escenario: Cambio de precio no afecta solicitudes existentes + Dado que hay una solicitud pendiente con "Consulta clinica" a 10000 + Cuando cambio el precio de "Consulta clinica" a 12000 + Entonces la solicitud deberia mantener el precio de 10000 + + # ============================================ + # CONFIGURAR COMBOS Y DEPENDENCIAS + # ============================================ + + Escenario: Crear combo de servicios + Cuando voy a "Combos" + Y hago click en "Agregar combo" + Y configuro: + | campo | valor | + | Nombre | Plan Preventivo Felino | + | Servicios | Vacunacion, Desparasitacion, Antipulgas | + | Precio combo | 25000 | + | Descuento | 20% | + Y guardo + Entonces el combo deberia crearse + Y deberia aplicarse automaticamente cuando se seleccionen esos servicios + + Escenario: Configurar servicio dependiente + Dado que "Vacunacion" requiere "Consulta clinica" + Cuando configuro la dependencia + Entonces al seleccionar "Vacunacion" en el turnero + Y "Consulta clinica" deberia agregarse automaticamente + Y no deberia poder quitarse diff --git a/atlas/book/gherkin-samples/es/backoffice/04-reembolsos.feature b/atlas/book/gherkin-samples/es/backoffice/04-reembolsos.feature new file mode 100644 index 0000000..40e1e16 --- /dev/null +++ b/atlas/book/gherkin-samples/es/backoffice/04-reembolsos.feature @@ -0,0 +1,141 @@ +# language: es +# Fuente: album/book/ops-templates/backoffice/04-reembolsos.md +# Drive: 07. Finanzas y contabilidad/Reembolsos +# Tests Backend: pytest tests/contracts/payments/test_refunds.py +# Tests Frontend: npx playwright test admin-refunds.spec.ts + +Característica: Proceso de reembolso + + Como administrador + Quiero procesar reembolsos de pagos + Para resolver cancelaciones y problemas + + Antecedentes: + Dado que estoy logueado como administrador + Y estoy en el backoffice + + # ============================================ + # IDENTIFICAR PAGO A REEMBOLSAR + # ============================================ + + Escenario: Buscar solicitud pagada por ID de operacion + Cuando busco por ID de Mercado Pago "12345678" + Entonces deberia encontrar la solicitud asociada + Y deberia ver los detalles del pago + + Escenario: Ver detalles de pago + Dado que encontre una solicitud pagada + Cuando veo la seccion de pago + Entonces deberia ver: + | campo | + | Monto pagado | + | Fecha de pago | + | Metodo de pago | + | ID operacion MP | + | Estado en MP | + + # ============================================ + # PROCESAR REEMBOLSO + # ============================================ + + Escenario: Reembolso total exitoso + Dado que hay una solicitud pagada por 15000 + Y la visita fue cancelada + Cuando abro la solicitud + Y hago click en "Procesar reembolso" + Y selecciono "Reembolso total" + Y ingreso motivo "Cancelacion por indisponibilidad del veterinario" + Y confirmo el reembolso + Entonces deberia enviarse la solicitud de reembolso a Mercado Pago + Y deberia ver mensaje "Reembolso en proceso" + Y la solicitud deberia registrar el reembolso + Y el dueno deberia recibir email de confirmacion + + Escenario: Reembolso parcial + Dado que hay una solicitud pagada por 20000 + Y solo se realizo parte del servicio + Cuando proceso reembolso parcial por 10000 + Y ingreso motivo "Servicio parcialmente completado" + Y confirmo + Entonces deberia procesarse reembolso por 10000 + Y deberia quedar registro del monto reembolsado + Y deberia quedar registro del monto retenido + + # ============================================ + # TIEMPOS DE ACREDITACION + # ============================================ + + Esquema del escenario: Informar tiempo de acreditacion segun metodo + Dado que el pago original fue con "" + Cuando proceso el reembolso + Entonces deberia informar al usuario: + """ + El reembolso se acreditara en + """ + + Ejemplos: + | metodo | tiempo | + | Tarjeta credito | 1-2 resumenes de cuenta | + | Tarjeta debito | 5-10 dias habiles | + | Dinero en cuenta MP | forma inmediata | + | Transferencia | 5-10 dias habiles | + + # ============================================ + # VALIDACIONES Y ERRORES + # ============================================ + + Escenario: No puedo reembolsar mas del monto pagado + Dado que hay una solicitud pagada por 15000 + Cuando intento reembolsar 20000 + Entonces deberia ver error "El monto supera el pago original" + + Escenario: Reembolso duplicado + Dado que ya procese un reembolso total para una solicitud + Cuando intento procesar otro reembolso + Entonces deberia ver error "Esta solicitud ya fue reembolsada" + + Escenario: Pago fuera de plazo de reembolso + Dado que hay un pago de hace 200 dias + Cuando intento reembolsar + Entonces deberia ver advertencia "Fuera de plazo de reembolso de MP (180 dias)" + Y deberia sugerir "Contactar al dueno para solucion alternativa" + + # ============================================ + # SEGUIMIENTO + # ============================================ + + Escenario: Ver estado del reembolso + Dado que procese un reembolso hace 2 dias + Cuando veo el detalle del reembolso + Entonces deberia ver el estado actual en Mercado Pago + Y deberia ver historial de estados: + | estado | fecha | + | Solicitado | 01/01/2024 | + | En proceso | 01/01/2024 | + | Completado | 03/01/2024 | + + Escenario: Reembolso rechazado por MP + Dado que Mercado Pago rechazo el reembolso + Cuando veo el estado + Entonces deberia ver "Reembolso rechazado" + Y deberia ver el motivo del rechazo + Y deberia poder contactar al dueno con alternativas + + # ============================================ + # REGISTRO Y AUDITORIA + # ============================================ + + Regla: Todos los reembolsos quedan registrados + + Escenario: Ver historial de reembolsos + Cuando voy a "Reportes" -> "Reembolsos" + Entonces deberia ver lista de todos los reembolsos + Y cada uno deberia mostrar: + | campo | + | Solicitud | + | Monto original | + | Monto reembolso | + | Motivo | + | Procesado por | + | Fecha | + | Estado | diff --git a/atlas/book/gherkin-samples/es/backoffice/05-reportes.feature b/atlas/book/gherkin-samples/es/backoffice/05-reportes.feature new file mode 100644 index 0000000..d7016aa --- /dev/null +++ b/atlas/book/gherkin-samples/es/backoffice/05-reportes.feature @@ -0,0 +1,155 @@ +# language: es +# Fuente: album/book/ops-templates/backoffice/05-reportes.md +# Drive: 07. Finanzas y contabilidad/Reportes +# Tests Backend: pytest tests/contracts/mascotas/test_stats.py +# Tests Frontend: npx playwright test admin-reports.spec.ts + +Característica: Reportes y dashboard + + Como administrador o gerente + Quiero ver metricas y generar reportes + Para tomar decisiones informadas + + Antecedentes: + Dado que estoy logueado como administrador + Y estoy en el backoffice + + # ============================================ + # DASHBOARD + # ============================================ + + Escenario: Ver dashboard principal + Cuando accedo al dashboard + Entonces deberia ver metricas resumidas: + | metrica | periodo | + | Solicitudes nuevas | Hoy | + | Visitas completadas | Semana | + | Ingresos | Mes | + | Veterinarios activos | Actual | + | Tasa de conversion | Mes | + + Escenario: Dashboard actualiza periodicamente + Dado que estoy en el dashboard + Cuando pasan 5 minutos + Entonces los datos deberian actualizarse + Y deberia ver indicador de ultima actualizacion + + Escenario: Ver grafico de tendencia + Cuando veo el grafico de solicitudes + Entonces deberia ver la evolucion de los ultimos 30 dias + Y deberia poder comparar con periodo anterior + + # ============================================ + # REPORTE DE SOLICITUDES + # ============================================ + + Escenario: Generar reporte de solicitudes + Cuando voy a "Reportes" -> "Solicitudes" + Y selecciono periodo "Enero 2024" + Y hago click en "Generar" + Entonces deberia ver tabla con solicitudes del periodo + Y deberia ver totales por estado: + | estado | cantidad | + | Pendiente | X | + | Coordinado | X | + | Pagado | X | + | Completado | X | + | Cancelado | X | + + Escenario: Filtrar reporte por multiples criterios + Cuando genero reporte con filtros: + | filtro | valor | + | Periodo | Enero 2024 | + | Zona | Palermo | + | Veterinario | Dra. Garcia | + | Estado | Completado | + Entonces deberia ver solo solicitudes que cumplan todos los criterios + + # ============================================ + # REPORTE DE INGRESOS + # ============================================ + + Escenario: Ver ingresos por periodo + Cuando voy a "Reportes" -> "Ingresos" + Y selecciono "Ultimo trimestre" + Entonces deberia ver: + | dato | + | Ingresos totales | + | Cantidad de pagos | + | Ticket promedio | + | Reembolsos realizados | + | Ingreso neto | + + Escenario: Ingresos agrupados por veterinario + Cuando agrupo el reporte por "Veterinario" + Entonces deberia ver para cada vet: + | dato | + | Visitas completadas | + | Ingresos generados | + | Porcentaje del total | + Y deberian estar ordenados de mayor a menor + + Escenario: Ingresos agrupados por servicio + Cuando agrupo el reporte por "Servicio" + Entonces deberia ver los servicios mas vendidos + Y deberia ver el ingreso de cada uno + Y deberia ver la cantidad de veces realizado + + # ============================================ + # REPORTE DE VETERINARIOS + # ============================================ + + Escenario: Ver performance de veterinarios + Cuando voy a "Reportes" -> "Veterinarios" + Entonces deberia ver para cada veterinario: + | metrica | + | Visitas completadas | + | Calificacion promedio | + | Tasa de aceptacion | + | Tasa de cancelacion | + | Ingresos generados | + + Escenario: Identificar veterinarios con problemas + Dado que el reporte muestra la tasa de cancelacion + Cuando ordeno por "Tasa de cancelacion" descendente + Entonces deberia ver primero los vets con mas cancelaciones + Y deberia poder investigar los motivos + + # ============================================ + # EXPORTACION + # ============================================ + + Esquema del escenario: Exportar reporte en diferentes formatos + Dado que tengo un reporte generado + Cuando hago click en "Exportar" + Y selecciono formato "" + Entonces deberia descargarse el archivo en formato + + Ejemplos: + | formato | + | CSV | + | Excel | + | PDF | + + Escenario: Exportar reporte grande de forma asincrona + Dado que genere un reporte con mas de 10000 registros + Cuando hago click en "Exportar" + Entonces deberia ver mensaje "Generando exportacion..." + Y deberia recibir notificacion cuando este listo + Y deberia poder descargar desde el centro de notificaciones + + # ============================================ + # REPORTES PROGRAMADOS + # ============================================ + + Escenario: Programar reporte semanal + Cuando voy a "Reportes" -> "Programados" + Y creo nuevo reporte programado: + | campo | valor | + | Tipo | Ingresos | + | Frecuencia | Semanal | + | Dia | Lunes | + | Destinatarios | gerencia@ejemplo.com | + Y guardo + Entonces deberia enviarse el reporte cada lunes + Y los destinatarios deberian recibirlo por email diff --git a/atlas/book/gherkin-samples/es/pet-owner/01-registro.feature b/atlas/book/gherkin-samples/es/pet-owner/01-registro.feature new file mode 100644 index 0000000..97c720e --- /dev/null +++ b/atlas/book/gherkin-samples/es/pet-owner/01-registro.feature @@ -0,0 +1,92 @@ +# language: es +# Fuente: album/book/ops-templates/pet-owner/01-registro.md +# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario +# Tests Backend: pytest tests/contracts/common/test_users.py +# Tests Frontend: npx playwright test auth.spec.ts + +Característica: Registro de usuario + + Como visitante de la plataforma + Quiero poder crear una cuenta + Para gestionar mis mascotas y reservar turnos + + # ============================================ + # CAMINO FELIZ + # ============================================ + + Escenario: Registro exitoso con datos validos + Dado que estoy en la pagina de registro + Cuando ingreso email "nuevo@ejemplo.com" + Y ingreso contraseña "Password123" + Y confirmo contraseña "Password123" + Y acepto los terminos y condiciones + Y hago click en "Crear cuenta" + Entonces deberia ver mensaje "Te enviamos un email de verificacion" + Y deberia recibir email de verificacion + + Escenario: Verificar email y activar cuenta + Dado que me registre con email "nuevo@ejemplo.com" + Y recibi el email de verificacion + Cuando hago click en el link de verificacion + Entonces mi cuenta deberia estar activa + Y deberia ser redirigido al dashboard + + # ============================================ + # VALIDACIONES + # ============================================ + + Esquema del escenario: Registro con datos invalidos + Dado que estoy en la pagina de registro + Cuando ingreso email "" + Y ingreso contraseña "" + Y confirmo contraseña "" + Y hago click en "Crear cuenta" + Entonces deberia ver error "" + + Ejemplos: + | email | password | confirmacion | mensaje_error | + | invalido | Password123 | Password123 | Email invalido | + | test@test.com | 123 | 123 | Contraseña muy corta | + | test@test.com | password | password | Debe contener al menos un numero | + | test@test.com | Password123 | Diferente123 | Las contraseñas no coinciden | + + Escenario: Registro con email ya existente + Dado que existe un usuario con email "existente@ejemplo.com" + Y estoy en la pagina de registro + Cuando ingreso email "existente@ejemplo.com" + Y completo el resto del formulario correctamente + Y hago click en "Crear cuenta" + Entonces deberia ver error "Este email ya esta registrado" + Y deberia ver link "Recuperar contraseña" + + # ============================================ + # CASOS ESPECIALES + # ============================================ + + Escenario: Registro linkea con cuenta invitado existente + # Usuario que reservo turno como invitado y ahora quiere registrarse + Dado que existe un usuario invitado con email "invitado@ejemplo.com" + Y ese usuario tiene una mascota "Luna" registrada + Y estoy en la pagina de registro + Cuando me registro con email "invitado@ejemplo.com" + Y verifico mi cuenta + Entonces deberia ver mi mascota "Luna" en el dashboard + Y deberia ver mis turnos anteriores + + Escenario: Registro desde flujo de turnero + # Usuario empezo a reservar turno y decide crear cuenta + Dado que estoy en el paso final del turnero + Y ingrese mis datos de contacto + Cuando hago click en "Crear cuenta para guardar mis datos" + Entonces deberia ver formulario simplificado + Y mi email ya deberia estar pre-llenado + Y solo deberia ingresar contraseña + + Escenario: Reenviar email de verificacion + Dado que me registre pero no verifique mi cuenta + Y estoy en la pagina de login + Cuando intento iniciar sesion + Entonces deberia ver "Tu cuenta no esta verificada" + Y deberia ver boton "Reenviar email" + Cuando hago click en "Reenviar email" + Entonces deberia recibir nuevo email de verificacion diff --git a/atlas/book/gherkin-samples/es/pet-owner/02-reservar-turno.feature b/atlas/book/gherkin-samples/es/pet-owner/02-reservar-turno.feature new file mode 100644 index 0000000..f28e208 --- /dev/null +++ b/atlas/book/gherkin-samples/es/pet-owner/02-reservar-turno.feature @@ -0,0 +1,141 @@ +# language: es +# Fuente: album/book/ops-templates/pet-owner/02-reservar-turno.md +# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario +# Tests Backend: pytest tests/contracts/workflows/test_turnero_general.py +# Tests Frontend: npx playwright test turnero.spec.ts +# Relacionado: def/work_plan/10-flow-turnero.md + +Característica: Reservar turno veterinario (Turnero) + + Como dueno de mascota + Quiero reservar un turno veterinario a domicilio + Para que atiendan a mi mascota sin salir de casa + + Antecedentes: + Dado que estoy en la pagina del turnero + + # ============================================ + # VERIFICACION DE COBERTURA + # ============================================ + + Escenario: Verificar cobertura en zona disponible + Cuando ingreso direccion "Av Santa Fe 1234, CABA" + Entonces deberia ver mensaje "Tenemos cobertura en tu zona" + Y deberia poder continuar al siguiente paso + + Escenario: Zona sin cobertura + Cuando ingreso direccion "Calle Principal 100, Ushuaia" + Entonces deberia ver mensaje "Aun no tenemos cobertura en tu zona" + Y deberia ver formulario "Avisame cuando lleguen" + + # ============================================ + # FLUJO COMPLETO POR TIPO DE USUARIO + # ============================================ + + Esquema del escenario: Reservar turno como + Dado que soy un usuario + Y tengo cobertura en mi zona + Cuando completo los datos de mi mascota: + | campo | valor | + | nombre | Luna | + | tipo | Gato | + | edad | 2 años | + | castrada | Si | + Y selecciono servicios: + | servicio | + | Vacunacion | + Y selecciono fechas preferidas: + | fecha | franja | + | 2024-01-15 | Mañana | + | 2024-01-16 | Tarde | + Y completo datos de contacto con email "" + Y envio la solicitud + Entonces deberia crearse una solicitud en estado "Pendiente" + Y el dueno deberia ser + Y deberia recibir email de confirmacion + + Ejemplos: + | tipo_usuario | email | estado_dueno | + | invitado | nuevo@test.com | creado como invitado | + | registrado | user@test.com | mi cuenta existente | + | recurrente | conocido@test.com | identificado por email | + + # ============================================ + # SELECCION DE SERVICIOS + # ============================================ + + Escenario: Servicios filtrados por tipo de mascota + Dado que agregue una mascota tipo "Gato" + Cuando veo los servicios disponibles + Entonces deberia ver "Vacuna triple felina" + Y deberia ver "Vacuna antirabica" + Pero no deberia ver "Vacuna sextuple canina" + + Escenario: Consulta clinica se agrega automaticamente con vacunacion + Dado que estoy seleccionando servicios + Cuando selecciono "Vacunacion" + Entonces "Consulta clinica" deberia agregarse automaticamente + Y deberia ver nota "Incluye revision general" + Y no deberia poder quitar "Consulta clinica" + + Escenario: Servicios combo con descuento + Dado que estoy seleccionando servicios + Cuando agrego los siguientes servicios: + | servicio | + | Vacunacion | + | Desparasitacion | + | Antipulgas | + Entonces deberia ver "Plan preventivo completo" + Y el total deberia incluir descuento de combo + + Escenario: Castracion no disponible para mascota castrada + Dado que mi mascota esta marcada como castrada + Cuando veo los servicios disponibles + Entonces no deberia ver "Castracion" + + # ============================================ + # DATOS DE CONTACTO Y CUENTA + # ============================================ + + Escenario: Pre-llenado de datos para usuario logueado + Dado que estoy logueado como "maria@ejemplo.com" + Y tengo registrada mascota "Firulais" + Cuando inicio el flujo de turnero + Entonces mi direccion deberia estar pre-llenada + Y deberia poder seleccionar "Firulais" de mis mascotas + Y mis datos de contacto ya deberian estar completos + + Escenario: Detectar usuario existente por email + Dado que soy usuario invitado + Y existe una cuenta con email "existente@ejemplo.com" + Cuando ingreso email "existente@ejemplo.com" en datos de contacto + Entonces deberia ver "Ya tenes cuenta con este email" + Y deberia ver opciones: + | opcion | + | Iniciar sesion | + | Continuar como invitado | + + # ============================================ + # EDGE CASES + # ============================================ + + Escenario: Usuario abandona flujo a mitad + Dado que complete los datos de mascota + Y cerre el navegador sin enviar + Cuando vuelvo a la pagina del turnero + Entonces deberia poder recuperar mi progreso + # Nota: datos guardados en localStorage o session + + Escenario: Multiples mascotas en una solicitud + Dado que quiero atender a 2 mascotas + Cuando agrego mascota "Luna" tipo "Gato" + Y agrego mascota "Rocky" tipo "Perro" + Y selecciono servicios para cada una + Entonces deberia crearse una solicitud con 2 mascotas + Y el precio deberia reflejar ambas + + Escenario: Franja horaria especifica + Dado que solo puedo por la mañana + Cuando selecciono franja "Mañana (9-12hs)" + Entonces la solicitud deberia registrar esa preferencia + # Nota: Es preferencia, no garantia diff --git a/atlas/book/gherkin-samples/es/pet-owner/03-gestion-mascotas.feature b/atlas/book/gherkin-samples/es/pet-owner/03-gestion-mascotas.feature new file mode 100644 index 0000000..99c2939 --- /dev/null +++ b/atlas/book/gherkin-samples/es/pet-owner/03-gestion-mascotas.feature @@ -0,0 +1,154 @@ +# language: es +# Fuente: album/book/ops-templates/pet-owner/03-gestion-mascotas.md +# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario +# Tests Backend: pytest tests/contracts/mascotas/test_pets.py +# Tests Frontend: npx playwright test pets.spec.ts + +Característica: Gestion de mascotas + + Como dueno de mascota registrado + Quiero gestionar la informacion de mis mascotas + Para tenerla actualizada y acceder a su historial medico + + Antecedentes: + Dado que estoy logueado como dueno de mascota + Y estoy en la seccion "Mis mascotas" + + # ============================================ + # AGREGAR MASCOTA + # ============================================ + + Escenario: Agregar mascota con datos minimos + Cuando hago click en "Agregar mascota" + Y completo el formulario: + | campo | valor | + | nombre | Luna | + | tipo | Gato | + Y hago click en "Guardar" + Entonces deberia ver "Luna" en mi lista de mascotas + Y Luna deberia aparecer disponible en el turnero + + Escenario: Agregar mascota con datos completos + Cuando hago click en "Agregar mascota" + Y completo el formulario: + | campo | valor | + | nombre | Rocky | + | tipo | Perro | + | raza | Labrador | + | fecha_nacimiento | 2020-03-15 | + | peso | 25 | + | sexo | Macho | + | castrado | Si | + Y subo una foto de Rocky + Y hago click en "Guardar" + Entonces deberia ver "Rocky" con su foto en mi lista + Y deberia ver badge "Castrado" + + Esquema del escenario: Validacion de datos de mascota + Cuando intento agregar mascota con igual a "" + Entonces deberia ver error "" + + Ejemplos: + | campo | valor | mensaje | + | nombre | | El nombre es obligatorio | + | nombre | A | Nombre muy corto | + | tipo | | Selecciona el tipo | + | peso | -5 | El peso debe ser positivo | + | peso | 500 | Peso fuera de rango | + + # ============================================ + # EDITAR MASCOTA + # ============================================ + + Escenario: Editar peso de mascota + Dado que tengo una mascota "Luna" con peso 4kg + Cuando edito a Luna + Y cambio el peso a 5kg + Y guardo los cambios + Entonces Luna deberia mostrar peso "5 kg" + + Escenario: Marcar mascota como castrada + Dado que tengo una mascota "Rocky" no castrado + Cuando edito a Rocky + Y marco "Esta castrado" + Y guardo los cambios + Entonces Rocky deberia mostrar badge "Castrado" + Y el servicio "Castracion" no deberia aparecer para Rocky en el turnero + + Escenario: Actualizar foto de mascota + Dado que tengo una mascota "Luna" sin foto + Cuando edito a Luna + Y subo una nueva foto + Y guardo los cambios + Entonces deberia ver la foto de Luna en su tarjeta + + # ============================================ + # ELIMINAR MASCOTA + # ============================================ + + Escenario: Eliminar mascota sin historial + Dado que tengo una mascota "Nuevo" sin visitas + Cuando hago click en "Eliminar" para Nuevo + Y confirmo la eliminacion + Entonces Nuevo no deberia aparecer en mi lista + # Nota: Es soft delete + + Escenario: Eliminar mascota con historial medico + Dado que tengo una mascota "Luna" con visitas anteriores + Cuando hago click en "Eliminar" para Luna + Entonces deberia ver advertencia "Luna tiene historial medico" + Y deberia ver "El historial se conservara pero no podras verlo" + Cuando confirmo la eliminacion + Entonces Luna no deberia aparecer en mi lista + + Escenario: No puedo eliminar mascota con turno pendiente + Dado que tengo una mascota "Rocky" con turno pendiente + Cuando intento eliminar a Rocky + Entonces deberia ver error "Rocky tiene turnos pendientes" + Y deberia ver sugerencia "Cancela los turnos primero" + + # ============================================ + # VER HISTORIAL MEDICO + # ============================================ + + Escenario: Ver historial de visitas de mascota + Dado que tengo una mascota "Luna" con 3 visitas completadas + Cuando hago click en Luna + Y voy a la seccion "Historial" + Entonces deberia ver 3 visitas listadas + Y deberian estar ordenadas por fecha descendente + + Escenario: Ver detalle de visita + Dado que tengo una mascota "Luna" con visitas + Cuando veo el historial de Luna + Y hago click en la primera visita + Entonces deberia ver: + | campo | + | Fecha | + | Veterinario | + | Diagnostico | + | Tratamiento | + | Medicamentos | + + Escenario: Mascota sin historial + Dado que tengo una mascota "Nuevo" recien agregada + Cuando veo el perfil de Nuevo + Entonces la seccion "Historial" deberia estar vacia + Y deberia ver mensaje "Aun no hay visitas registradas" + Y deberia ver boton "Reservar primer turno" + + # ============================================ + # CASOS ESPECIALES + # ============================================ + + Escenario: Mascota heredada de cuenta invitado + Dado que me registre con email "juan@test.com" + Y previamente reserve turno como invitado para "Firulais" + Cuando voy a "Mis mascotas" + Entonces deberia ver "Firulais" en mi lista + Y deberia ver su historial de visitas previas + + Escenario: Razas filtradas por tipo + Cuando agrego una mascota tipo "Gato" + Entonces las razas disponibles deberian ser razas de gato + Y no deberia ver razas de perro como "Labrador" diff --git a/atlas/book/gherkin-samples/es/pet-owner/04-pago-turno.feature b/atlas/book/gherkin-samples/es/pet-owner/04-pago-turno.feature new file mode 100644 index 0000000..2c9e11f --- /dev/null +++ b/atlas/book/gherkin-samples/es/pet-owner/04-pago-turno.feature @@ -0,0 +1,141 @@ +# language: es +# Fuente: album/book/ops-templates/pet-owner/04-pago-turno.md +# Drive: 07. Finanzas y contabilidad/Mercado Pago +# Tests Backend: pytest tests/contracts/payments/test_mercadopago.py +# Tests Frontend: npx playwright test payment.spec.ts + +Característica: Pago de turno + + Como dueno de mascota con turno coordinado + Quiero pagar mi turno online + Para confirmar la visita del veterinario + + Antecedentes: + Dado que tengo un turno en estado "Coordinado" + Y el turno tiene asignado veterinario "Dra. Garcia" + Y la fecha asignada es "15 de enero a las 10:00" + + # ============================================ + # FLUJO DE PAGO EXITOSO + # ============================================ + + Escenario: Pagar turno con tarjeta de credito + Dado que estoy en el detalle de mi turno coordinado + Cuando hago click en "Pagar" + Entonces deberia ser redirigido a Mercado Pago + Y deberia ver el monto correcto + + Cuando selecciono "Tarjeta de credito" + Y completo los datos de la tarjeta + Y confirmo el pago + Entonces deberia volver a la plataforma + Y deberia ver "Pago exitoso" + Y el turno deberia estar en estado "Pagado" + + Escenario: Recibir confirmacion de pago + Dado que complete el pago exitosamente + Entonces deberia recibir email de confirmacion + Y el email deberia contener: + | campo | + | Fecha del turno | + | Direccion | + | Veterinario asignado | + | Monto pagado | + | Numero de operacion | + + # ============================================ + # METODOS DE PAGO + # ============================================ + + Esquema del escenario: Pagar con diferentes metodos + Dado que estoy en Mercado Pago + Cuando selecciono metodo "" + Y completo el pago + Entonces el pago deberia ser + Y el estado de acreditacion deberia ser "" + + Ejemplos: + | metodo | resultado | acreditacion | + | Tarjeta credito | exitoso | inmediata | + | Tarjeta debito | exitoso | inmediata | + | Dinero en cuenta | exitoso | inmediata | + | Transferencia | pendiente | 1-2 dias | + | Rapipago | pendiente | hasta 24hs | + + # ============================================ + # MANEJO DE ERRORES + # ============================================ + + Escenario: Pago rechazado por fondos insuficientes + Dado que estoy en Mercado Pago + Cuando intento pagar con tarjeta sin fondos + Entonces deberia ver error "Fondos insuficientes" + Y deberia poder reintentar con otra tarjeta + Y el turno deberia seguir en estado "Coordinado" + + Escenario: Usuario cancela el pago + Dado que estoy en Mercado Pago + Cuando hago click en "Volver al sitio" + Entonces deberia volver a la plataforma + Y deberia ver mensaje "El pago fue cancelado" + Y deberia ver boton "Reintentar pago" + Y el turno deberia seguir en estado "Coordinado" + + Escenario: Cierre de browser durante pago + Dado que estoy en Mercado Pago + Y cierro el navegador accidentalmente + Cuando vuelvo a la plataforma + Y voy a "Mis turnos" + Entonces deberia poder ver el estado real del pago + # Si se proceso: Pagado. Si no: Coordinado con opcion de pagar + + # ============================================ + # CASOS ESPECIALES + # ============================================ + + Escenario: Link de pago expirado + Dado que recibi el link de pago hace mas de 24 horas + Cuando hago click en el link + Entonces deberia ver "Este link ha expirado" + Y deberia ver "Contacta a soporte para generar uno nuevo" + + Escenario: Intento pagar turno ya pagado + Dado que mi turno ya esta en estado "Pagado" + Cuando accedo al link de pago + Entonces deberia ver "Este turno ya fue pagado" + Y deberia ver boton "Ver detalle del turno" + + Escenario: Precio cambio desde la coordinacion + # Caso muy raro pero posible + Dado que el precio del servicio aumento desde que se coordino + Cuando voy a pagar + Entonces deberia ver el precio original acordado + # El precio se congela al momento de coordinacion + + # ============================================ + # INTEGRACION CON WEBHOOK + # ============================================ + + Regla: El estado del turno se actualiza via webhook de Mercado Pago + + Escenario: Webhook confirma pago aprobado + Dado que el usuario completo el pago en Mercado Pago + Cuando Mercado Pago envia webhook con status "approved" + Entonces el turno deberia cambiar a estado "Pagado" + Y el veterinario deberia recibir notificacion + Y el usuario deberia recibir email de confirmacion + + Escenario: Webhook informa pago pendiente + Dado que el usuario pago con transferencia bancaria + Cuando Mercado Pago envia webhook con status "pending" + Entonces el turno deberia quedarse en "Coordinado" + Y deberia registrarse el pago pendiente + Y el usuario deberia recibir email "Esperando acreditacion" + + Escenario: Webhook falla pero pago se proceso + # Caso de error que requiere intervencion manual + Dado que el usuario pago exitosamente + Pero el webhook fallo por error de red + Entonces el turno seguira en "Coordinado" + Y el equipo de ops deberia recibir alerta + Y deberian poder actualizar manualmente diff --git a/atlas/book/gherkin-samples/es/pet-owner/05-historial-medico.feature b/atlas/book/gherkin-samples/es/pet-owner/05-historial-medico.feature new file mode 100644 index 0000000..e56fc4e --- /dev/null +++ b/atlas/book/gherkin-samples/es/pet-owner/05-historial-medico.feature @@ -0,0 +1,148 @@ +# language: es +# Fuente: album/book/ops-templates/pet-owner/05-historial-medico.md +# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario +# Tests Backend: pytest tests/contracts/mascotas/test_vet_visits.py +# Tests Frontend: npx playwright test medical-history.spec.ts + +Característica: Ver historial medico + + Como dueno de mascota + Quiero ver el historial medico de mis mascotas + Para tener registro de sus visitas y tratamientos + + Antecedentes: + Dado que estoy logueado como dueno de mascota + Y tengo una mascota "Luna" con visitas completadas + + # ============================================ + # ACCESO AL HISTORIAL + # ============================================ + + Escenario: Acceder al historial desde mascotas + Dado que estoy en "Mis mascotas" + Cuando hago click en "Luna" + Y voy a la seccion "Historial de visitas" + Entonces deberia ver lista de visitas de Luna + + Escenario: Acceder al historial desde turnos + Dado que estoy en "Mis turnos" + Y filtro por "Completados" + Cuando hago click en un turno de Luna + Y hago click en "Ver informe medico" + Entonces deberia ver el informe de esa visita + + # ============================================ + # VISUALIZACION DE VISITAS + # ============================================ + + Escenario: Ver lista de visitas ordenadas + Dado que Luna tiene visitas en las siguientes fechas: + | fecha | + | 2024-01-15 | + | 2023-11-20 | + | 2023-06-10 | + Cuando veo el historial de Luna + Entonces deberia ver las visitas ordenadas de mas reciente a mas antigua + Y la primera deberia ser del "15 de enero 2024" + + Escenario: Ver detalle completo de informe + Cuando hago click en una visita completada + Entonces deberia ver los siguientes datos: + | seccion | contenido | + | Informacion | Fecha, veterinario, servicios | + | Examen fisico | Peso, temperatura, FC, obs | + | Diagnostico | Descripcion del diagnostico | + | Tratamiento | Plan de tratamiento | + | Medicamentos | Lista con dosis y frecuencia | + | Estudios | Estudios solicitados y estado | + | Seguimiento | Recomendaciones, proximo control | + + Escenario: Ver informe con medicamentos recetados + Dado que la visita incluye medicamentos: + """ + 1. Amoxicilina 250mg - 1 comprimido cada 12hs por 7 dias + 2. Meloxicam 1.5mg - 1 comprimido por dia por 3 dias + """ + Cuando veo el informe + Entonces deberia ver la lista de medicamentos + Y cada medicamento deberia mostrar: + | campo | + | Nombre | + | Dosis | + | Frecuencia | + | Duracion | + + # ============================================ + # DESCARGA Y COMPARTIR + # ============================================ + + Escenario: Descargar informe como PDF + Dado que estoy viendo un informe medico + Cuando hago click en "Descargar PDF" + Entonces deberia descargarse un archivo PDF + Y el PDF deberia contener toda la informacion del informe + Y deberia tener el logo de Amar Mascotas + + Escenario: Imprimir informe + Dado que estoy viendo un informe medico + Cuando hago click en "Imprimir" + Entonces deberia abrirse el dialogo de impresion + Y el formato deberia ser optimizado para impresion + + # ============================================ + # CASOS ESPECIALES + # ============================================ + + Escenario: Visita completada sin informe cargado + # El vet no cargo el informe todavia + Dado que Luna tiene una visita marcada como "Completada" + Pero el veterinario no cargo el informe aun + Cuando intento ver el informe + Entonces deberia ver mensaje "Informe pendiente" + Y deberia ver "El veterinario esta completando el informe" + + Escenario: Informe con estudios pendientes + Dado que el informe solicita estudios de laboratorio + Y los resultados aun no estan disponibles + Cuando veo el informe + Entonces deberia ver seccion "Estudios solicitados" + Y deberia ver estado "Pendiente de resultados" + Y deberia ver nota "Se notificara cuando esten listos" + + Escenario: Mascota con multiples visitas el mismo dia + # Raro pero posible: consulta de urgencia + seguimiento + Dado que Luna tuvo 2 visitas el 15 de enero + Cuando veo el historial + Entonces deberia ver ambas visitas listadas + Y deberian estar diferenciadas por hora + + # ============================================ + # PERMISOS Y PRIVACIDAD + # ============================================ + + Regla: Solo el dueno puede ver el historial de su mascota + + Escenario: Dueno ve historial de su mascota + Dado que soy dueno de Luna + Cuando accedo al historial de Luna + Entonces deberia poder verlo completo + + Escenario: No puedo ver historial de mascota ajena + Dado que existe mascota "Rocky" de otro dueno + Cuando intento acceder al historial de Rocky + Entonces deberia ver error "No tienes acceso" + # 403 Forbidden + + # ============================================ + # BUSQUEDA Y FILTROS + # ============================================ + + Escenario: Filtrar historial por tipo de servicio + Dado que Luna tiene visitas de vacunacion y consulta + Cuando filtro por "Vacunacion" + Entonces solo deberia ver visitas de vacunacion + + Escenario: Buscar en historial por fecha + Cuando busco visitas del "2023" + Entonces solo deberia ver visitas del 2023 + Y no deberia ver visitas del 2024 diff --git a/atlas/book/gherkin-samples/es/veterinarian/01-aceptar-solicitud.feature b/atlas/book/gherkin-samples/es/veterinarian/01-aceptar-solicitud.feature new file mode 100644 index 0000000..9792239 --- /dev/null +++ b/atlas/book/gherkin-samples/es/veterinarian/01-aceptar-solicitud.feature @@ -0,0 +1,129 @@ +# language: es +# Fuente: album/book/ops-templates/veterinarian/01-aceptar-solicitud.md +# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario +# Tests Backend: pytest tests/contracts/solicitudes/test_service_requests.py +# Tests Frontend: npx playwright test vet-requests.spec.ts + +Característica: Aceptar o rechazar solicitudes de servicio + + Como veterinario de la plataforma + Quiero revisar y responder a solicitudes en mi zona + Para gestionar mi agenda de visitas a domicilio + + Antecedentes: + Dado que estoy logueado como veterinario + Y tengo cobertura en los barrios "Palermo" y "Recoleta" + Y estoy en el dashboard de veterinario + + # ============================================ + # VER SOLICITUDES PENDIENTES + # ============================================ + + Escenario: Ver lista de solicitudes en mi zona + Cuando veo la seccion "Solicitudes pendientes" + Entonces deberia ver solo solicitudes de "Palermo" y "Recoleta" + Y no deberia ver solicitudes de otros barrios + + Escenario: Ver detalle de solicitud + Dado que hay una solicitud pendiente + Cuando hago click en la solicitud + Entonces deberia ver: + | seccion | contenido | + | Dueno | Nombre, telefono, direccion | + | Mascota | Nombre, tipo, edad, foto | + | Servicios | Lista de servicios solicitados | + | Fechas | Fechas preferidas por el dueno | + | Historial | Visitas anteriores si las hay | + + # ============================================ + # ACEPTAR SOLICITUD + # ============================================ + + Escenario: Aceptar solicitud con fecha disponible + Dado que hay una solicitud para el barrio "Palermo" + Y el dueno prefiere fechas: + | fecha | franja | + | 2024-01-15 | Mañana | + | 2024-01-16 | Tarde | + Y tengo disponibilidad el 15 de enero a las 10:00 + Cuando hago click en "Aceptar" + Y selecciono fecha "15 de enero" hora "10:00" + Y confirmo la aceptacion + Entonces la solicitud deberia pasar a estado "Coordinado" + Y deberia quedar asignada a mi + Y el dueno deberia recibir notificacion con mis datos + Y la visita deberia aparecer en mi agenda + + Escenario: Aceptar solicitud con datos de mascota que ya atendi + Dado que hay una solicitud para mascota "Luna" + Y yo atendi a "Luna" anteriormente + Cuando veo el detalle de la solicitud + Entonces deberia ver badge "Paciente recurrente" + Y deberia ver el historial de mis visitas anteriores a Luna + + # ============================================ + # RECHAZAR SOLICITUD + # ============================================ + + Esquema del escenario: Rechazar solicitud con motivo + Dado que hay una solicitud pendiente + Cuando hago click en "Rechazar" + Y selecciono motivo "" + Y confirmo el rechazo + Entonces la solicitud deberia desaparecer de mi lista + Y deberia seguir visible para otros veterinarios + + Ejemplos: + | motivo | + | No tengo disponibilidad | + | Fuera de mi zona | + | No realizo este servicio | + | Otro | + + Escenario: Rechazar sin seleccionar motivo + Dado que hay una solicitud pendiente + Cuando hago click en "Rechazar" + Y confirmo sin seleccionar motivo + Entonces deberia poder rechazar igual + # El motivo es opcional + + # ============================================ + # RACE CONDITIONS + # ============================================ + + Regla: Solo un veterinario puede aceptar cada solicitud + + Escenario: Otro vet acepta mientras estoy viendo + Dado que estoy viendo el detalle de una solicitud + Y otro veterinario acepta la misma solicitud + Cuando intento aceptarla + Entonces deberia ver error "Esta solicitud ya fue aceptada" + Y deberia ser redirigido al listado + + Escenario: Solicitud desaparece cuando otro la acepta + Dado que estoy viendo el listado de solicitudes + Y otro veterinario acepta una solicitud + Entonces esa solicitud deberia desaparecer de mi lista + # Idealmente en tiempo real via websocket + + # ============================================ + # CASOS ESPECIALES + # ============================================ + + Escenario: Solicitud urgente destacada + Dado que hay una solicitud marcada como "Urgente" + Cuando veo el listado de solicitudes + Entonces deberia ver la solicitud con indicador de urgencia + Y deberia aparecer primero en la lista + + Escenario: Solicitud con multiples mascotas + Dado que hay una solicitud para 2 mascotas + Cuando veo el detalle + Entonces deberia ver informacion de ambas mascotas + Y deberia ver nota sobre tiempo estimado adicional + + Escenario: No puedo aceptar con agenda completa + Dado que tengo mi agenda completa para las fechas de la solicitud + Cuando intento aceptar la solicitud + Entonces deberia ver advertencia "No tienes disponibilidad en las fechas preferidas" + Y deberia poder proponer una fecha alternativa diff --git a/atlas/book/gherkin-samples/es/veterinarian/02-gestion-agenda.feature b/atlas/book/gherkin-samples/es/veterinarian/02-gestion-agenda.feature new file mode 100644 index 0000000..3fe6149 --- /dev/null +++ b/atlas/book/gherkin-samples/es/veterinarian/02-gestion-agenda.feature @@ -0,0 +1,134 @@ +# language: es +# Fuente: album/book/ops-templates/veterinarian/02-gestion-agenda.md +# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario +# Tests Backend: pytest tests/contracts/mascotas/test_vet_availability.py +# Tests Frontend: npx playwright test vet-schedule.spec.ts + +Característica: Gestion de agenda veterinaria + + Como veterinario + Quiero gestionar mi calendario y disponibilidad + Para organizar mis visitas a domicilio eficientemente + + Antecedentes: + Dado que estoy logueado como veterinario + Y estoy en la seccion "Mi agenda" + + # ============================================ + # VER AGENDA + # ============================================ + + Escenario: Ver calendario semanal + Cuando veo mi agenda en vista semanal + Entonces deberia ver los 7 dias de la semana + Y deberia ver mis visitas programadas + Y cada visita deberia mostrar hora y nombre del paciente + + Escenario: Ver visitas con codigo de color por estado + Dado que tengo visitas en diferentes estados + Cuando veo mi agenda + Entonces las visitas deberian mostrarse con colores: + | estado | color | + | Coordinado | amarillo | + | Pagado | verde | + | En progreso | azul | + | Completado | gris | + + Escenario: Ver detalle de visita desde el calendario + Dado que tengo una visita programada para hoy + Cuando hago click en la visita + Entonces deberia ver popup con: + | campo | valor | + | Hora | 10:00 - 11:00 | + | Dueno | Maria Garcia | + | Direccion | Av Santa Fe 1234 | + | Mascota | Luna (Gato, 3 años) | + | Servicios | Vacunacion, Consulta | + | Estado pago | Pagado | + Y deberia ver boton "Ver detalle completo" + + # ============================================ + # CONFIGURAR DISPONIBILIDAD SEMANAL + # ============================================ + + Escenario: Configurar horario laboral + Cuando voy a "Configuracion" -> "Mi disponibilidad" + Y configuro mi horario: + | dia | trabajo | desde | hasta | pausa_desde | pausa_hasta | + | Lunes | Si | 09:00 | 18:00 | 13:00 | 14:00 | + | Martes | Si | 09:00 | 18:00 | 13:00 | 14:00 | + | Miercoles | Si | 09:00 | 18:00 | 13:00 | 14:00 | + | Jueves | Si | 09:00 | 18:00 | 13:00 | 14:00 | + | Viernes | Si | 09:00 | 15:00 | | | + | Sabado | No | | | | | + | Domingo | No | | | | | + Y guardo la configuracion + Entonces mi disponibilidad deberia actualizarse + Y no deberia recibir solicitudes fuera de ese horario + + # ============================================ + # BLOQUEAR DIAS ESPECIFICOS + # ============================================ + + Escenario: Marcar dia como no disponible + Dado que necesito el 20 de enero libre + Cuando hago click en el dia 20 de enero en el calendario + Y selecciono "Marcar como no disponible" + Y ingreso motivo "Vacaciones" + Y confirmo + Entonces el dia 20 deberia mostrarse como bloqueado + Y no deberia poder aceptar solicitudes para ese dia + + Escenario: Bloquear rango de fechas + Dado que tomo vacaciones del 15 al 22 de enero + Cuando voy a "Agregar indisponibilidad" + Y selecciono fecha inicio "15 de enero" + Y selecciono fecha fin "22 de enero" + Y ingreso motivo "Vacaciones" + Y confirmo + Entonces todos esos dias deberian mostrarse como bloqueados + + Escenario: Desbloquear dia + Dado que tengo el 20 de enero bloqueado + Y ya no necesito ese dia libre + Cuando hago click en el dia bloqueado + Y selecciono "Quitar bloqueo" + Entonces el dia deberia estar disponible nuevamente + + # ============================================ + # IMPACTO EN SOLICITUDES + # ============================================ + + Regla: Los bloqueos no afectan visitas ya aceptadas + + Escenario: Bloquear dia con visita ya aceptada + Dado que tengo una visita aceptada para el 15 de enero + Cuando intento bloquear el 15 de enero + Entonces deberia ver advertencia "Tienes una visita programada ese dia" + Y deberia poder elegir: + | opcion | + | Cancelar el bloqueo | + | Bloquear y contactar al dueno | + + Regla: Cambios de disponibilidad solo afectan solicitudes futuras + + Escenario: Cambiar horario no afecta visitas existentes + Dado que tengo una visita a las 17:00 el lunes + Cuando cambio mi horario del lunes para terminar a las 16:00 + Y guardo los cambios + Entonces la visita de las 17:00 deberia mantenerse + Y deberia ver advertencia sobre la inconsistencia + + # ============================================ + # NAVEGACION + # ============================================ + + Escenario: Navegar entre semanas + Cuando hago click en "Semana siguiente" + Entonces deberia ver el calendario de la proxima semana + Y deberia poder volver con "Semana anterior" + + Escenario: Ir a fecha especifica + Cuando hago click en el selector de fecha + Y selecciono "15 de marzo" + Entonces deberia ver la semana que contiene el 15 de marzo diff --git a/atlas/book/gherkin-samples/es/veterinarian/03-realizar-visita.feature b/atlas/book/gherkin-samples/es/veterinarian/03-realizar-visita.feature new file mode 100644 index 0000000..a5ecc4f --- /dev/null +++ b/atlas/book/gherkin-samples/es/veterinarian/03-realizar-visita.feature @@ -0,0 +1,159 @@ +# language: es +# Fuente: album/book/ops-templates/veterinarian/03-realizar-visita.md +# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario +# Tests Backend: pytest tests/contracts/mascotas/test_vet_visits.py +# Tests Frontend: npx playwright test vet-visit.spec.ts + +Característica: Realizar visita y crear informe medico + + Como veterinario + Quiero registrar la atencion medica de mis visitas + Para mantener el historial clinico del paciente + + Antecedentes: + Dado que estoy logueado como veterinario + Y tengo una visita programada para hoy con mascota "Luna" + Y la visita esta en estado "Pagado" + + # ============================================ + # INICIAR VISITA + # ============================================ + + Escenario: Ver informacion antes de la visita + Cuando accedo al detalle de la visita + Entonces deberia ver la direccion para llegar + Y deberia ver el telefono del dueno para confirmar + Y deberia ver los servicios a realizar + Y deberia ver el historial previo de Luna + + Escenario: Iniciar visita al llegar + Dado que llegue al domicilio + Cuando hago click en "Iniciar visita" + Entonces el estado deberia cambiar a "En progreso" + Y deberia registrarse la hora de inicio + Y deberia habilitarse el boton "Crear informe" + + # ============================================ + # CREAR INFORME MEDICO + # ============================================ + + Escenario: Completar informe con examen fisico + Dado que la visita esta en progreso + Cuando voy a "Crear informe" + Y completo el examen fisico: + | campo | valor | + | Peso | 4.5 kg | + | Temperatura | 38.5 °C | + | Frecuencia cardiaca | 120 lpm | + | Frecuencia resp | 25 rpm | + | Mucosas | Rosadas | + | Hidratacion | Normal | + Y guardo el informe + Entonces el examen fisico deberia guardarse + + Escenario: Agregar diagnostico y tratamiento + Dado que complete el examen fisico + Cuando agrego el diagnostico: + """ + Otitis externa bilateral leve. + Paciente presenta prurito y secrecion ceruminosa. + Sin signos de infeccion secundaria. + """ + Y agrego el tratamiento: + """ + Limpieza de oidos con solucion fisiologica. + Aplicacion de gotas oticas antibioticas. + Control en 7 dias. + """ + Y guardo el informe + Entonces el diagnostico y tratamiento deberian guardarse + + Escenario: Recetar medicamentos + Dado que estoy creando el informe + Cuando agrego medicamentos: + | nombre | dosis | frecuencia | duracion | + | Otomax gotas | 5 gotas | cada 12 horas | 7 dias | + | Meloxicam 1.5mg | 1 comp | cada 24 horas | 3 dias | + Y guardo el informe + Entonces los medicamentos deberian aparecer en el informe + Y el dueno podra verlos desde su cuenta + + Escenario: Solicitar estudios complementarios + Dado que necesito estudios de laboratorio + Cuando agrego estudio: + | tipo | indicaciones | + | Hemograma completo | Ayuno de 8 horas | + | Perfil hepatico | Para control pre-quirurgico | + Y guardo el informe + Entonces los estudios deberian quedar registrados + Y deberian mostrarse como "Pendientes de resultado" + + # ============================================ + # COMPLETAR VISITA + # ============================================ + + Escenario: Completar visita con informe + Dado que el informe esta completo + Cuando hago click en "Completar visita" + Y confirmo la finalizacion + Entonces la visita deberia pasar a estado "Completado" + Y deberia generarse factura electronica (AFIP) + Y el dueno deberia recibir notificacion + Y el informe deberia ser visible para el dueno + + Escenario: No puedo completar sin informe + Dado que la visita esta en progreso + Pero no cree ningun informe + Cuando intento completar la visita + Entonces deberia ver error "Debes crear el informe antes de completar" + + # ============================================ + # GUARDADO AUTOMATICO + # ============================================ + + Regla: El informe se guarda automaticamente + + Escenario: Guardado automatico cada 30 segundos + Dado que estoy escribiendo el informe + Cuando pasan 30 segundos + Entonces deberia ver indicador "Guardado automaticamente" + + Escenario: Recuperar informe despues de desconexion + Dado que estaba escribiendo el informe + Y perdi conexion a internet + Cuando recupero la conexion + Entonces deberia recuperar mi progreso + Y no deberia perder lo que escribi + + # ============================================ + # CASOS ESPECIALES + # ============================================ + + Escenario: Visita con multiples mascotas + Dado que la visita incluye a "Luna" y "Rocky" + Cuando creo el informe + Entonces deberia crear un informe separado para cada mascota + Y ambos informes deberian estar asociados a la misma visita + + Escenario: Cancelar visita in situ + Dado que llegue al domicilio + Pero el dueno no esta + Cuando marco la visita como "Cancelada in situ" + Y selecciono motivo "Dueno ausente" + Entonces la visita deberia marcarse como cancelada + Y deberia generarse cargo por visita fallida + # Segun politicas de la empresa + + Escenario: Emergencia durante visita rutinaria + Dado que estoy en una consulta de vacunacion + Y detecto una condicion que requiere atencion urgente + Cuando agrego servicio "Consulta de urgencia" + Entonces deberia poder documentar ambos servicios + Y el precio deberia ajustarse + + Escenario: Derivar a clinica + Dado que el paciente necesita atencion en clinica + Cuando marco "Derivacion a clinica" + Y especifico el motivo + Entonces deberia quedar registrada la derivacion + Y el dueno deberia recibir instrucciones diff --git a/atlas/book/gherkin-samples/es/veterinarian/04-zonas-cobertura.feature b/atlas/book/gherkin-samples/es/veterinarian/04-zonas-cobertura.feature new file mode 100644 index 0000000..6056215 --- /dev/null +++ b/atlas/book/gherkin-samples/es/veterinarian/04-zonas-cobertura.feature @@ -0,0 +1,121 @@ +# language: es +# Fuente: album/book/ops-templates/veterinarian/04-zonas-cobertura.md +# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario +# Tests Backend: pytest tests/contracts/mascotas/test_veterinarians.py +# Tests Frontend: npx playwright test vet-coverage.spec.ts + +Característica: Gestionar zonas de cobertura + + Como veterinario + Quiero definir en que zonas atiendo + Para recibir solo solicitudes que puedo cubrir + + Antecedentes: + Dado que estoy logueado como veterinario + Y estoy en la seccion "Mi cobertura" + + # ============================================ + # VER ZONAS ACTUALES + # ============================================ + + Escenario: Ver mapa con mis zonas de cobertura + Cuando cargo la pagina de cobertura + Entonces deberia ver un mapa de la ciudad + Y mis zonas cubiertas deberian estar resaltadas + Y deberia ver un listado de barrios seleccionados + + Escenario: Ver estadisticas por zona + Dado que tengo cobertura en "Palermo" y "Recoleta" + Cuando veo el detalle de mis zonas + Entonces deberia ver para cada zona: + | metrica | + | Solicitudes este mes | + | Visitas completadas | + | Otros vets en la zona | + + # ============================================ + # AGREGAR ZONAS + # ============================================ + + Escenario: Agregar barrio desde el mapa + Dado que no tengo cobertura en "Belgrano" + Cuando hago click en "Belgrano" en el mapa + Y confirmo agregar la zona + Entonces "Belgrano" deberia aparecer en mi lista de zonas + Y deberia empezar a ver solicitudes de Belgrano + + Escenario: Agregar barrio desde el buscador + Cuando busco "Nuñez" en el buscador de barrios + Y selecciono "Nuñez" de los resultados + Y confirmo agregar la zona + Entonces "Nuñez" deberia aparecer en mi lista + + Escenario: Agregar multiples zonas a la vez + Cuando selecciono los barrios: + | barrio | + | Colegiales | + | Chacarita | + | Villa Crespo | + Y hago click en "Agregar seleccionados" + Entonces los 3 barrios deberian agregarse a mi cobertura + + # ============================================ + # QUITAR ZONAS + # ============================================ + + Escenario: Quitar zona de cobertura + Dado que tengo cobertura en "Recoleta" + Cuando hago click en "X" junto a "Recoleta" + Y confirmo quitar la zona + Entonces "Recoleta" no deberia estar en mi lista + Y no deberia ver nuevas solicitudes de Recoleta + + Escenario: Quitar zona con solicitudes pendientes + Dado que tengo cobertura en "Palermo" + Y hay solicitudes pendientes en Palermo que no acepte + Cuando intento quitar "Palermo" + Entonces deberia ver advertencia "Hay X solicitudes pendientes en esta zona" + Y deberia poder confirmar de todas formas + # Las solicitudes pendientes siguen disponibles para otros vets + + # ============================================ + # IMPACTO EN SOLICITUDES + # ============================================ + + Regla: Los cambios de zona afectan solo solicitudes nuevas + + Escenario: Agregar zona muestra solicitudes existentes + Dado que no tengo cobertura en "Belgrano" + Y hay 3 solicitudes pendientes en Belgrano + Cuando agrego "Belgrano" a mi cobertura + Entonces deberia ver las 3 solicitudes pendientes de Belgrano + + Escenario: Quitar zona no afecta visitas aceptadas + Dado que tengo cobertura en "Recoleta" + Y tengo una visita aceptada en Recoleta para mañana + Cuando quito "Recoleta" de mi cobertura + Entonces la visita de mañana deberia mantenerse + Pero no deberia ver nuevas solicitudes de Recoleta + + # ============================================ + # CASOS ESPECIALES + # ============================================ + + Escenario: Zona no disponible en el sistema + Dado que quiero cubrir "Barrio Nuevo" + Pero ese barrio no esta en el sistema + Cuando busco "Barrio Nuevo" + Entonces deberia ver mensaje "Zona no encontrada" + Y deberia ver opcion "Solicitar que agreguen esta zona" + + Escenario: Zona con baja demanda + Dado que agrego una zona con poca actividad + Cuando pasan 30 dias sin solicitudes + Entonces deberia recibir sugerencia "Considera expandir tu cobertura" + + Escenario: Zona saturada de veterinarios + Dado que quiero agregar "Palermo" + Y hay muchos veterinarios cubriendo Palermo + Cuando agrego la zona + Entonces deberia ver nota "Esta zona tiene alta cobertura de veterinarios" + # Informativo, no bloquea diff --git a/atlas/book/gherkin-samples/es/veterinarian/05-historial-pacientes.feature b/atlas/book/gherkin-samples/es/veterinarian/05-historial-pacientes.feature new file mode 100644 index 0000000..16c9915 --- /dev/null +++ b/atlas/book/gherkin-samples/es/veterinarian/05-historial-pacientes.feature @@ -0,0 +1,157 @@ +# language: es +# Fuente: album/book/ops-templates/veterinarian/05-historial-pacientes.md +# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario +# Tests Backend: pytest tests/contracts/mascotas/test_vet_visits.py +# Tests Frontend: npx playwright test vet-history.spec.ts + +Característica: Ver historial de pacientes + + Como veterinario + Quiero acceder al historial medico de pacientes + Para tener contexto clinico en mis atenciones + + Antecedentes: + Dado que estoy logueado como veterinario + Y estoy en la seccion "Historia clinica" + + # ============================================ + # BUSCAR PACIENTES + # ============================================ + + Esquema del escenario: Buscar paciente por diferentes criterios + Cuando busco por con valor "" + Entonces deberia ver resultados que coincidan + + Ejemplos: + | criterio | valor | + | nombre dueno | Maria Garcia | + | nombre mascota | Luna | + | telefono | 1155551234 | + | email | maria@ejemplo.com | + + Escenario: Busqueda sin resultados + Cuando busco "ZZZZZ paciente inexistente" + Entonces deberia ver mensaje "No se encontraron resultados" + Y deberia ver sugerencia "Verifica la ortografia" + + Escenario: Busqueda con multiples resultados + Cuando busco "Garcia" + Y hay varios duenos con apellido Garcia + Entonces deberia ver lista de coincidencias + Y cada resultado deberia mostrar: + | campo | + | Nombre completo | + | Email | + | Mascotas | + + # ============================================ + # VER FICHA DE MASCOTA + # ============================================ + + Escenario: Ver ficha completa de mascota + Dado que encontre a la mascota "Luna" + Cuando hago click en Luna + Entonces deberia ver la ficha con: + | seccion | contenido | + | Datos basicos | Nombre, tipo, raza, edad, peso | + | Foto | Foto de la mascota | + | Dueno | Nombre y contacto del dueno | + | Vacunacion | Estado de vacunas | + | Historial | Lista de visitas | + + Escenario: Ver grafico de peso historico + Dado que estoy viendo la ficha de "Luna" + Y Luna tuvo multiples visitas con peso registrado + Cuando veo la seccion "Evolucion" + Entonces deberia ver grafico de peso en el tiempo + Y deberia poder detectar tendencias + + # ============================================ + # VER HISTORIAL DE VISITAS + # ============================================ + + Escenario: Ver listado de visitas + Dado que estoy viendo la ficha de "Luna" + Y Luna tiene 5 visitas completadas + Cuando veo la seccion "Historial de visitas" + Entonces deberia ver las 5 visitas listadas + Y deberian estar ordenadas de mas reciente a mas antigua + Y cada visita deberia mostrar: + | campo | + | Fecha | + | Veterinario | + | Servicios | + | Diagnostico | + + Escenario: Ver informe de visita de otro veterinario + Dado que Luna fue atendida por "Dra. Rodriguez" + Y yo no la atendi en esa visita + Cuando hago click en esa visita + Entonces deberia poder ver el informe completo + # Para continuidad de atencion + + Escenario: Ver informe detallado + Cuando hago click en una visita + Entonces deberia ver el informe con: + | seccion | contenido | + | Examen fisico | Peso, temperatura, signos vitales | + | Diagnostico | Descripcion del diagnostico | + | Tratamiento | Plan de tratamiento indicado | + | Medicamentos | Lista con dosis y duracion | + | Estudios | Estudios solicitados y resultados | + | Observaciones | Notas adicionales | + + # ============================================ + # FILTROS Y NAVEGACION + # ============================================ + + Escenario: Filtrar historial por tipo de servicio + Dado que estoy viendo el historial de "Luna" + Cuando filtro por servicio "Vacunacion" + Entonces solo deberia ver visitas de vacunacion + + Escenario: Filtrar historial por fecha + Dado que estoy viendo el historial de "Luna" + Cuando filtro por año "2023" + Entonces solo deberia ver visitas del 2023 + + Escenario: Filtrar por mis atenciones + Dado que estoy viendo el historial de "Luna" + Y Luna fue atendida por varios veterinarios + Cuando marco "Solo mis atenciones" + Entonces solo deberia ver las visitas que yo realice + + # ============================================ + # PERMISOS + # ============================================ + + Regla: Veterinarios pueden ver historial de pacientes que atendieron + + Escenario: Puedo ver historial de paciente que atendi + Dado que yo atendi a "Luna" al menos una vez + Cuando busco a Luna + Entonces deberia poder ver su historial completo + + Escenario: Puedo ver historial de paciente con solicitud pendiente + Dado que hay una solicitud pendiente para "Rocky" + Y la solicitud esta en mi zona + Cuando busco a Rocky + Entonces deberia poder ver su historial + # Para evaluar si acepto la solicitud + + # ============================================ + # CASOS ESPECIALES + # ============================================ + + Escenario: Paciente sin historial previo + Dado que busco a "Nuevo" que nunca fue atendido + Cuando veo su ficha + Entonces el historial deberia estar vacio + Y deberia ver mensaje "Sin visitas registradas" + + Escenario: Dueno con multiples mascotas + Dado que busque al dueno "Maria Garcia" + Y Maria tiene 3 mascotas + Cuando veo su perfil + Entonces deberia ver las 3 mascotas listadas + Y deberia poder navegar al historial de cada una diff --git a/atlas/book/gherkin-samples/index.html b/atlas/book/gherkin-samples/index.html new file mode 100644 index 0000000..b295873 --- /dev/null +++ b/atlas/book/gherkin-samples/index.html @@ -0,0 +1,248 @@ + + + + + + Gherkin Samples - Album + + + +
+
+ +

Gherkin Samples

+

BDD feature files - Given/When/Then specifications derived from ops templates

+ Sample Data +
+ +
+ + +
+ + + + + + + + +
+ + + + diff --git a/atlas/index.html b/atlas/index.html new file mode 100644 index 0000000..4fe5f89 --- /dev/null +++ b/atlas/index.html @@ -0,0 +1,160 @@ + + + + + + Album · Pawprint + + + + +
+ + +

Album

+ {% if pawprint_url %}← Pawprint{% endif %} +
+

conocimiento y toma de decisiones

+ +
+
+

Books

+
+

Template

+

Larder

+
+
+
+ +
+

Books

+
    + {% for book in books %} +
  • + {{ book.title }} + {{ book.status | capitalize }} +
  • + {% else %} +
  • --Pending
  • + {% endfor %} +
+
+ +
+

Templates

+
    + {% for template in templates %} +
  • {{ template.title }}{{ template.status | capitalize }}
  • + {% else %} +
  • --Pending
  • + {% endfor %} +
+
+ +
+

Larders

+
    + {% for larder in larders %} +
  • {{ larder.title }}{{ larder.status | capitalize }}
  • + {% else %} +
  • --Pending
  • + {% endfor %} +
+
+ +
+ {% if pawprint_url %}← Pawprint{% else %}← Pawprint{% endif %} +
+ + diff --git a/atlas/main.py b/atlas/main.py new file mode 100644 index 0000000..93d25c0 --- /dev/null +++ b/atlas/main.py @@ -0,0 +1,369 @@ +""" +Album - Documentation system. +""" + +import os +import httpx +from pathlib import Path +from fastapi import FastAPI, Request +from fastapi.responses import HTMLResponse +from fastapi.staticfiles import StaticFiles +from fastapi.templating import Jinja2Templates + +app = FastAPI(title="Album", version="0.1.0") + +BASE_DIR = Path(__file__).parent.resolve() +BOOK_DIR = BASE_DIR / "book" +STATIC_DIR = BASE_DIR / "static" + +# Create static directory if it doesn't exist +STATIC_DIR.mkdir(exist_ok=True) + +templates = Jinja2Templates(directory=str(BASE_DIR)) + +# Serve static files +app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static") + +# Pawprint URL for data fetching +PAWPRINT_URL = os.getenv("PAWPRINT_URL", "http://localhost:12000") + + +def get_data(): + """Fetch data from pawprint hub.""" + try: + resp = httpx.get(f"{PAWPRINT_URL}/api/data/album", timeout=5.0) + if resp.status_code == 200: + return resp.json() + except Exception as e: + print(f"Failed to fetch data from pawprint: {e}") + return {"templates": [], "larders": [], "books": []} + + +@app.get("/health") +def health(): + return {"status": "ok", "service": "album"} + + +@app.get("/") +def index(request: Request): + data = get_data() + return templates.TemplateResponse("index.html", { + "request": request, + "pawprint_url": os.getenv("PAWPRINT_EXTERNAL_URL", PAWPRINT_URL), + **data, + }) + + +@app.get("/api/data") +def api_data(): + """API endpoint for frontend data (proxied from pawprint).""" + return get_data() + + +# --- Book: Feature Flow (HTML presentations) --- +@app.get("/book/feature-flow/", response_class=HTMLResponse) +@app.get("/book/feature-flow", response_class=HTMLResponse) +def feature_flow_index(): + """Redirect to English presentation by default""" + return """ +Feature Flow + + +

Feature Flow - Standardization Pipeline

+ +""" + + +@app.get("/book/feature-flow/en", response_class=HTMLResponse) +def feature_flow_en(): + html_file = BOOK_DIR / "feature-flow" / "index-en.html" + return HTMLResponse(html_file.read_text()) + + +@app.get("/book/feature-flow/es", response_class=HTMLResponse) +def feature_flow_es(): + html_file = BOOK_DIR / "feature-flow" / "index-es.html" + return HTMLResponse(html_file.read_text()) + + +# --- Book: Feature Form Samples (templated book) --- +@app.get("/book/feature-form-samples/", response_class=HTMLResponse) +@app.get("/book/feature-form-samples", response_class=HTMLResponse) +def feature_form_samples_index(request: Request): + """Templated book landing page""" + data = get_data() + book = next((b for b in data.get("books", []) if b["slug"] == "feature-form-samples"), None) + if not book: + return HTMLResponse("

Book not found

", status_code=404) + return templates.TemplateResponse("book-template.html", { + "request": request, + "book": book, + }) + + +@app.get("/book/feature-form-samples/template/", response_class=HTMLResponse) +@app.get("/book/feature-form-samples/template", response_class=HTMLResponse) +def feature_form_samples_template(): + """View the template - styled like actual feature forms""" + html = """ + + + + + Feature Form Template · Album + + + +
+
+ +

Feature Form Template

+
+ Template +
+
+ +
+
+

[Nombre del Flujo]

+
+
+
+ +
[Dueno de mascota / Veterinario / Admin]
+
+
+ +
[Que pagina/boton/link]
+
+
+ +
[Objetivo en una oracion]
+
+
+ +
+
    +
  1. [Primera cosa que hace el usuario]
  2. +
  3. [Segunda cosa que hace el usuario]
  4. +
  5. [etc.]
  6. +
+
+
+
+ +
[Resultado esperado cuando todo funciona]
+
+
+ +
[Problema 1]
[Problema 2]
+
+
+ +
[Caso especial 1]
[Caso especial 2]
+
+
+ +
[Otros flujos que se conectan con este]
+
+
+ +
[Notas para el equipo de desarrollo]
+
+
+
+ + +
+ +""" + return HTMLResponse(html) + + +@app.get("/book/feature-form-samples/larder/", response_class=HTMLResponse) +@app.get("/book/feature-form-samples/larder", response_class=HTMLResponse) +def feature_form_samples_larder(): + """Browse the larder (actual data)""" + html_file = BOOK_DIR / "feature-form-samples" / "index.html" + if html_file.exists(): + return HTMLResponse(html_file.read_text()) + return HTMLResponse("

Larder index not found

", status_code=404) + + +@app.get("/book/feature-form-samples/larder/{user_type}/{filename}", response_class=HTMLResponse) +def feature_form_samples_detail(request: Request, user_type: str, filename: str): + """View a specific feature form""" + # Look in the larder subfolder (feature-form) + larder_dir = BOOK_DIR / "feature-form-samples" / "feature-form" + file_path = larder_dir / user_type / filename + if not file_path.exists(): + return HTMLResponse("

Not found

", status_code=404) + + content = file_path.read_text() + detail_file = BOOK_DIR / "feature-form-samples" / "detail.html" + return templates.TemplateResponse(str(detail_file.relative_to(BASE_DIR)), { + "request": request, + "user_type": user_type, + "filename": filename, + "content": content, + }) + + +# --- Book: Gherkin Samples --- +@app.get("/book/gherkin-samples/", response_class=HTMLResponse) +@app.get("/book/gherkin-samples", response_class=HTMLResponse) +@app.get("/book/gherkin/", response_class=HTMLResponse) # Alias +@app.get("/book/gherkin", response_class=HTMLResponse) # Alias +def gherkin_samples_index(): + """Browse gherkin samples""" + html_file = BOOK_DIR / "gherkin-samples" / "index.html" + return HTMLResponse(html_file.read_text()) + + +@app.get("/book/gherkin-samples/{lang}/{user_type}/{filename}", response_class=HTMLResponse) +@app.get("/book/gherkin/{lang}/{user_type}/{filename}", response_class=HTMLResponse) # Alias +def gherkin_samples_detail(request: Request, lang: str, user_type: str, filename: str): + """View a specific gherkin file""" + file_path = BOOK_DIR / "gherkin-samples" / lang / user_type / filename + if not file_path.exists() or not file_path.suffix == ".feature": + return HTMLResponse("

Not found

", status_code=404) + + content = file_path.read_text() + detail_file = BOOK_DIR / "gherkin-samples" / "detail.html" + return templates.TemplateResponse(str(detail_file.relative_to(BASE_DIR)), { + "request": request, + "lang": lang, + "user_type": user_type, + "filename": filename, + "content": content, + }) + + +# --- Book: Architecture Model (static site) --- +app.mount("/book/arch-model", StaticFiles(directory=str(BOOK_DIR / "arch-model"), html=True), name="arch-model") + + +# --- Book: Drive Index --- +@app.get("/book/drive-index/", response_class=HTMLResponse) +@app.get("/book/drive-index", response_class=HTMLResponse) +def drive_index(): + """Browse drive index""" + html_file = BOOK_DIR / "drive-index" / "index.html" + return HTMLResponse(html_file.read_text()) + + +if __name__ == "__main__": + import uvicorn + uvicorn.run( + "main:app", + host="0.0.0.0", + port=int(os.getenv("PORT", "12002")), + reload=os.getenv("DEV", "").lower() in ("1", "true"), + ) diff --git a/atlas/requirements.txt b/atlas/requirements.txt new file mode 100644 index 0000000..a7d1b30 --- /dev/null +++ b/atlas/requirements.txt @@ -0,0 +1,4 @@ +fastapi>=0.104.0 +uvicorn>=0.24.0 +jinja2>=3.1.0 +httpx>=0.25.0 diff --git a/atlas/static/prism/prism-gherkin.min.js b/atlas/static/prism/prism-gherkin.min.js new file mode 100644 index 0000000..183dac7 --- /dev/null +++ b/atlas/static/prism/prism-gherkin.min.js @@ -0,0 +1 @@ +!function(a){var n="(?:\r?\n|\r)[ \t]*\\|.+\\|(?:(?!\\|).)*";a.languages.gherkin={pystring:{pattern:/("""|''')[\s\S]+?\1/,alias:"string"},comment:{pattern:/(^[ \t]*)#.*/m,lookbehind:!0},tag:{pattern:/(^[ \t]*)@\S*/m,lookbehind:!0},feature:{pattern:/((?:^|\r?\n|\r)[ \t]*)(?:Ability|Ahoy matey!|Arwedd|Aspekt|Besigheid Behoefte|Business Need|Caracteristica|Característica|Egenskab|Egenskap|Eiginleiki|Feature|Fīča|Fitur|Fonctionnalité|Fonksyonalite|Funcionalidade|Funcionalitat|Functionalitate|Funcţionalitate|Funcționalitate|Functionaliteit|Fungsi|Funkcia|Funkcija|Funkcionalitāte|Funkcionalnost|Funkcja|Funksie|Funktionalität|Funktionalitéit|Funzionalità|Hwaet|Hwæt|Jellemző|Karakteristik|Lastnost|Mak|Mogucnost|laH|Mogućnost|Moznosti|Možnosti|OH HAI|Omadus|Ominaisuus|Osobina|Özellik|Potrzeba biznesowa|perbogh|poQbogh malja'|Požadavek|Požiadavka|Pretty much|Qap|Qu'meH 'ut|Savybė|Tính năng|Trajto|Vermoë|Vlastnosť|Właściwość|Značilnost|Δυνατότητα|Λειτουργία|Могућност|Мөмкинлек|Особина|Свойство|Үзенчәлеклелек|Функционал|Функционалност|Функция|Функціонал|תכונה|خاصية|خصوصیت|صلاحیت|کاروبار کی ضرورت|وِیژگی|रूप लेख|ਖਾਸੀਅਤ|ਨਕਸ਼ ਨੁਹਾਰ|ਮੁਹਾਂਦਰਾ|గుణము|ಹೆಚ್ಚಳ|ความต้องการทางธุรกิจ|ความสามารถ|โครงหลัก|기능|フィーチャ|功能|機能):(?:[^:\r\n]+(?:\r?\n|\r|$))*/,lookbehind:!0,inside:{important:{pattern:/(:)[^\r\n]+/,lookbehind:!0},keyword:/[^:\r\n]+:/}},scenario:{pattern:/(^[ \t]*)(?:Abstract Scenario|Abstrakt Scenario|Achtergrond|Aer|Ær|Agtergrond|All y'all|Antecedentes|Antecedents|Atburðarás|Atburðarásir|Awww, look mate|B4|Background|Baggrund|Bakgrund|Bakgrunn|Bakgrunnur|Beispiele|Beispiller|Bối cảnh|Cefndir|Cenario|Cenário|Cenario de Fundo|Cenário de Fundo|Cenarios|Cenários|Contesto|Context|Contexte|Contexto|Conto|Contoh|Contone|Dæmi|Dasar|Dead men tell no tales|Delineacao do Cenario|Delineação do Cenário|Dis is what went down|Dữ liệu|Dyagram Senaryo|Dyagram senaryo|Egzanp|Ejemplos|Eksempler|Ekzemploj|Enghreifftiau|Esbozo do escenario|Escenari|Escenario|Esempi|Esquema de l'escenari|Esquema del escenario|Esquema do Cenario|Esquema do Cenário|EXAMPLZ|Examples|Exempel|Exemple|Exemples|Exemplos|First off|Fono|Forgatókönyv|Forgatókönyv vázlat|Fundo|Geçmiş|Grundlage|Hannergrond|ghantoH|Háttér|Heave to|Istorik|Juhtumid|Keadaan|Khung kịch bản|Khung tình huống|Kịch bản|Koncept|Konsep skenario|Kontèks|Kontekst|Kontekstas|Konteksts|Kontext|Konturo de la scenaro|Latar Belakang|lut chovnatlh|lut|lutmey|Lýsing Atburðarásar|Lýsing Dæma|MISHUN SRSLY|MISHUN|Menggariskan Senario|mo'|Náčrt Scenára|Náčrt Scénáře|Náčrt Scenáru|Oris scenarija|Örnekler|Osnova|Osnova Scenára|Osnova scénáře|Osnutek|Ozadje|Paraugs|Pavyzdžiai|Példák|Piemēri|Plan du scénario|Plan du Scénario|Plan Senaryo|Plan senaryo|Plang vum Szenario|Pozadí|Pozadie|Pozadina|Príklady|Příklady|Primer|Primeri|Primjeri|Przykłady|Raamstsenaarium|Reckon it's like|Rerefons|Scenár|Scénář|Scenarie|Scenarij|Scenarijai|Scenarijaus šablonas|Scenariji|Scenārijs|Scenārijs pēc parauga|Scenarijus|Scenario|Scénario|Scenario Amlinellol|Scenario Outline|Scenario Template|Scenariomal|Scenariomall|Scenarios|Scenariu|Scenariusz|Scenaro|Schema dello scenario|Se ðe|Se the|Se þe|Senario|Senaryo Deskripsyon|Senaryo deskripsyon|Senaryo|Senaryo taslağı|Shiver me timbers|Situācija|Situai|Situasie Uiteensetting|Situasie|Skenario konsep|Skenario|Skica|Structura scenariu|Structură scenariu|Struktura scenarija|Stsenaarium|Swa hwaer swa|Swa|Swa hwær swa|Szablon scenariusza|Szenario|Szenariogrundriss|Tapaukset|Tapaus|Tapausaihio|Taust|Tausta|Template Keadaan|Template Senario|Template Situai|The thing of it is|Tình huống|Variantai|Voorbeelde|Voorbeelden|Wharrimean is|Yo-ho-ho|You'll wanna|Założenia|Παραδείγματα|Περιγραφή Σεναρίου|Σενάρια|Σενάριο|Υπόβαθρο|Кереш|Контекст|Концепт|Мисаллар|Мисоллар|Основа|Передумова|Позадина|Предистория|Предыстория|Приклади|Пример|Примери|Примеры|Рамка на сценарий|Скица|Структура сценарија|Структура сценария|Структура сценарію|Сценарий|Сценарий структураси|Сценарийның төзелеше|Сценарији|Сценарио|Сценарій|Тарих|Үрнәкләр|דוגמאות|רקע|תבנית תרחיש|תרחיש|الخلفية|الگوی سناریو|امثلة|پس منظر|زمینه|سناریو|سيناريو|سيناريو مخطط|مثالیں|منظر نامے کا خاکہ|منظرنامہ|نمونه ها|उदाहरण|परिदृश्य|परिदृश्य रूपरेखा|पृष्ठभूमि|ਉਦਾਹਰਨਾਂ|ਪਟਕਥਾ|ਪਟਕਥਾ ਢਾਂਚਾ|ਪਟਕਥਾ ਰੂਪ ਰੇਖਾ|ਪਿਛੋਕੜ|ఉదాహరణలు|కథనం|నేపథ్యం|సన్నివేశం|ಉದಾಹರಣೆಗಳು|ಕಥಾಸಾರಾಂಶ|ವಿವರಣೆ|ಹಿನ್ನೆಲೆ|โครงสร้างของเหตุการณ์|ชุดของตัวอย่าง|ชุดของเหตุการณ์|แนวคิด|สรุปเหตุการณ์|เหตุการณ์|배경|시나리오|시나리오 개요|예|サンプル|シナリオ|シナリオアウトライン|シナリオテンプレ|シナリオテンプレート|テンプレ|例|例子|剧本|剧本大纲|劇本|劇本大綱|场景|场景大纲|場景|場景大綱|背景):[^:\r\n]*/m,lookbehind:!0,inside:{important:{pattern:/(:)[^\r\n]*/,lookbehind:!0},keyword:/[^:\r\n]+:/}},"table-body":{pattern:RegExp("("+n+")(?:"+n+")+"),lookbehind:!0,inside:{outline:{pattern:/<[^>]+>/,alias:"variable"},td:{pattern:/\s*[^\s|][^|]*/,alias:"string"},punctuation:/\|/}},"table-head":{pattern:RegExp(n),inside:{th:{pattern:/\s*[^\s|][^|]*/,alias:"variable"},punctuation:/\|/}},atrule:{pattern:/(^[ \t]+)(?:'a|'ach|'ej|7|a|A také|A taktiež|A tiež|A zároveň|Aber|Ac|Adott|Akkor|Ak|Aleshores|Ale|Ali|Allora|Alors|Als|Ama|Amennyiben|Amikor|Ampak|an|AN|Ananging|And y'all|And|Angenommen|Anrhegedig a|An|Apabila|Atès|Atesa|Atunci|Avast!|Aye|A|awer|Bagi|Banjur|Bet|Biết|Blimey!|Buh|But at the end of the day I reckon|But y'all|But|BUT|Cal|Când|Cand|Cando|Ce|Cuando|Če|Ða ðe|Ða|Dadas|Dada|Dados|Dado|DaH ghu' bejlu'|dann|Dann|Dano|Dan|Dar|Dat fiind|Data|Date fiind|Date|Dati fiind|Dati|Daţi fiind|Dați fiind|DEN|Dato|De|Den youse gotta|Dengan|Diberi|Diyelim ki|Donada|Donat|Donitaĵo|Do|Dun|Duota|Ðurh|Eeldades|Ef|Eğer ki|Entao|Então|Entón|E|En|Entonces|Epi|És|Etant donnée|Etant donné|Et|Étant données|Étant donnée|Étant donné|Etant données|Etant donnés|Étant donnés|Fakat|Gangway!|Gdy|Gegeben seien|Gegeben sei|Gegeven|Gegewe|ghu' noblu'|Gitt|Given y'all|Given|Givet|Givun|Ha|Cho|I CAN HAZ|In|Ir|It's just unbelievable|I|Ja|Jeśli|Jeżeli|Kad|Kada|Kadar|Kai|Kaj|Když|Keď|Kemudian|Ketika|Khi|Kiedy|Ko|Kuid|Kui|Kun|Lan|latlh|Le sa a|Let go and haul|Le|Lè sa a|Lè|Logo|Lorsqu'<|Lorsque|mä|Maar|Mais|Mając|Ma|Majd|Maka|Manawa|Mas|Men|Menawa|Mutta|Nalika|Nalikaning|Nanging|Når|När|Nato|Nhưng|Niin|Njuk|O zaman|Och|Og|Oletetaan|Ond|Onda|Oraz|Pak|Pero|Però|Podano|Pokiaľ|Pokud|Potem|Potom|Privzeto|Pryd|Quan|Quand|Quando|qaSDI'|Så|Sed|Se|Siis|Sipoze ke|Sipoze Ke|Sipoze|Si|Şi|Și|Soit|Stel|Tada|Tad|Takrat|Tak|Tapi|Ter|Tetapi|Tha the|Tha|Then y'all|Then|Thì|Thurh|Toda|Too right|Un|Und|ugeholl|Và|vaj|Vendar|Ve|wann|Wanneer|WEN|Wenn|When y'all|When|Wtedy|Wun|Y'know|Yeah nah|Yna|Youse know like when|Youse know when youse got|Y|Za predpokladu|Za předpokladu|Zadan|Zadani|Zadano|Zadate|Zadato|Zakładając|Zaradi|Zatati|Þa þe|Þa|Þá|Þegar|Þurh|Αλλά|Δεδομένου|Και|Όταν|Τότε|А також|Агар|Але|Али|Аммо|А|Әгәр|Әйтик|Әмма|Бирок|Ва|Вә|Дадено|Дано|Допустим|Если|Задате|Задати|Задато|И|І|К тому же|Када|Кад|Когато|Когда|Коли|Ләкин|Лекин|Нәтиҗәдә|Нехай|Но|Онда|Припустимо, що|Припустимо|Пусть|Также|Та|Тогда|Тоді|То|Унда|Һәм|Якщо|אבל|אזי|אז|בהינתן|וגם|כאשר|آنگاه|اذاً|اگر|اما|اور|با فرض|بالفرض|بفرض|پھر|تب|ثم|جب|عندما|فرض کیا|لكن|لیکن|متى|هنگامی|و|अगर|और|कदा|किन्तु|चूंकि|जब|तथा|तदा|तब|परन्तु|पर|यदि|ਅਤੇ|ਜਦੋਂ|ਜਿਵੇਂ ਕਿ|ਜੇਕਰ|ਤਦ|ਪਰ|అప్పుడు|ఈ పరిస్థితిలో|కాని|చెప్పబడినది|మరియు|ಆದರೆ|ನಂತರ|ನೀಡಿದ|ಮತ್ತು|ಸ್ಥಿತಿಯನ್ನು|กำหนดให้|ดังนั้น|แต่|เมื่อ|และ|그러면<|그리고<|단<|만약<|만일<|먼저<|조건<|하지만<|かつ<|しかし<|ただし<|ならば<|もし<|並且<|但し<|但是<|假如<|假定<|假設<|假设<|前提<|同时<|同時<|并且<|当<|當<|而且<|那么<|那麼<)(?=[ \t])/m,lookbehind:!0},string:{pattern:/"(?:\\.|[^"\\\r\n])*"|'(?:\\.|[^'\\\r\n])*'/,inside:{outline:{pattern:/<[^>]+>/,alias:"variable"}}},outline:{pattern:/<[^>]+>/,alias:"variable"}}}(Prism); \ No newline at end of file diff --git a/atlas/static/prism/prism-line-numbers.min.css b/atlas/static/prism/prism-line-numbers.min.css new file mode 100644 index 0000000..8170f64 --- /dev/null +++ b/atlas/static/prism/prism-line-numbers.min.css @@ -0,0 +1 @@ +pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right} \ No newline at end of file diff --git a/atlas/static/prism/prism-line-numbers.min.js b/atlas/static/prism/prism-line-numbers.min.js new file mode 100644 index 0000000..1f12d2d --- /dev/null +++ b/atlas/static/prism/prism-line-numbers.min.js @@ -0,0 +1 @@ +!function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document){var e="line-numbers",n=/\n(?!$)/g,t=Prism.plugins.lineNumbers={getLine:function(n,t){if("PRE"===n.tagName&&n.classList.contains(e)){var i=n.querySelector(".line-numbers-rows");if(i){var r=parseInt(n.getAttribute("data-start"),10)||1,s=r+(i.children.length-1);ts&&(t=s);var l=t-r;return i.children[l]}}},resize:function(e){r([e])},assumeViewportIndependence:!0},i=void 0;window.addEventListener("resize",(function(){t.assumeViewportIndependence&&i===window.innerWidth||(i=window.innerWidth,r(Array.prototype.slice.call(document.querySelectorAll("pre.line-numbers"))))})),Prism.hooks.add("complete",(function(t){if(t.code){var i=t.element,s=i.parentNode;if(s&&/pre/i.test(s.nodeName)&&!i.querySelector(".line-numbers-rows")&&Prism.util.isActive(i,e)){i.classList.remove(e),s.classList.add(e);var l,o=t.code.match(n),a=o?o.length+1:1,u=new Array(a+1).join("");(l=document.createElement("span")).setAttribute("aria-hidden","true"),l.className="line-numbers-rows",l.innerHTML=u,s.hasAttribute("data-start")&&(s.style.counterReset="linenumber "+(parseInt(s.getAttribute("data-start"),10)-1)),t.element.appendChild(l),r([s]),Prism.hooks.run("line-numbers",t)}}})),Prism.hooks.add("line-numbers",(function(e){e.plugins=e.plugins||{},e.plugins.lineNumbers=!0}))}function r(e){if(0!=(e=e.filter((function(e){var n,t=(n=e,n?window.getComputedStyle?getComputedStyle(n):n.currentStyle||null:null)["white-space"];return"pre-wrap"===t||"pre-line"===t}))).length){var t=e.map((function(e){var t=e.querySelector("code"),i=e.querySelector(".line-numbers-rows");if(t&&i){var r=e.querySelector(".line-numbers-sizer"),s=t.textContent.split(n);r||((r=document.createElement("span")).className="line-numbers-sizer",t.appendChild(r)),r.innerHTML="0",r.style.display="block";var l=r.getBoundingClientRect().height;return r.innerHTML="",{element:e,lines:s,lineHeights:[],oneLinerHeight:l,sizer:r}}})).filter(Boolean);t.forEach((function(e){var n=e.sizer,t=e.lines,i=e.lineHeights,r=e.oneLinerHeight;i[t.length-1]=void 0,t.forEach((function(e,t){if(e&&e.length>1){var s=n.appendChild(document.createElement("span"));s.style.display="block",s.textContent=e}else i[t]=r}))})),t.forEach((function(e){for(var n=e.sizer,t=e.lineHeights,i=0,r=0;rcode[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green} \ No newline at end of file diff --git a/atlas/static/prism/prism.min.js b/atlas/static/prism/prism.min.js new file mode 100644 index 0000000..054ce1e --- /dev/null +++ b/atlas/static/prism/prism.min.js @@ -0,0 +1 @@ +var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(l){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,e={},j={manual:l.Prism&&l.Prism.manual,disableWorkerMessageHandler:l.Prism&&l.Prism.disableWorkerMessageHandler,util:{encode:function e(t){return t instanceof C?new C(t.type,e(t.content),t.alias):Array.isArray(t)?t.map(e):t.replace(/&/g,"&").replace(/=i.reach);y+=b.value.length,b=b.next){var v=b.value;if(n.length>t.length)return;if(!(v instanceof C)){var F,x=1;if(m){if(!(F=L(f,y,t,p))||F.index>=t.length)break;var k=F.index,w=F.index+F[0].length,A=y;for(A+=b.value.length;A<=k;)b=b.next,A+=b.value.length;if(A-=b.value.length,y=A,b.value instanceof C)continue;for(var P=b;P!==n.tail&&(Ai.reach&&(i.reach=v),b.prev),S=(S&&(_=z(n,_,S),y+=S.length),O(n,_,x),new C(o,d?j.tokenize($,d):$,h,$));b=z(n,_,S),E&&z(n,b,E),1i.reach&&(i.reach=$.reach))}}}}}(e,r,t,r.head,0),r),i=[],o=s.head.next;o!==s.tail;)i.push(o.value),o=o.next;return i},hooks:{all:{},add:function(e,t){var n=j.hooks.all;n[e]=n[e]||[],n[e].push(t)},run:function(e,t){var n=j.hooks.all[e];if(n&&n.length)for(var a,r=0;a=n[r++];)a(t)}},Token:C};function C(e,t,n,a){this.type=e,this.content=t,this.alias=n,this.length=0|(a||"").length}function L(e,t,n,a){e.lastIndex=t;t=e.exec(n);return t&&a&&t[1]&&(e=t[1].length,t.index+=e,t[0]=t[0].slice(e)),t}function u(){var e={value:null,prev:null,next:null},t={value:null,prev:e,next:null};e.next=t,this.head=e,this.tail=t,this.length=0}function z(e,t,n){var a=t.next,n={value:n,prev:t,next:a};return t.next=n,a.prev=n,e.length++,n}function O(e,t,n){for(var a=t.next,r=0;r"+s.content+""},!l.document)return l.addEventListener&&(j.disableWorkerMessageHandler||l.addEventListener("message",function(e){var e=JSON.parse(e.data),t=e.language,n=e.code,e=e.immediateClose;l.postMessage(j.highlight(n,j.languages[t],t)),e&&l.close()},!1)),j;var a,e=j.util.currentScript();function r(){j.manual||j.highlightAll()}return e&&(j.filename=e.src,e.hasAttribute("data-manual")&&(j.manual=!0)),j.manual||("loading"===(a=document.readyState)||"interactive"===a&&e&&e.defer?document.addEventListener("DOMContentLoaded",r):window.requestAnimationFrame?window.requestAnimationFrame(r):window.setTimeout(r,16)),j}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism),Prism.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))}),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(e,t){var n={},n=(n["language-"+t]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[t]},n.cdata=/^$/i,{"included-cdata":{pattern://i,inside:n}}),t=(n["language-"+t]={pattern:/[\s\S]+/,inside:Prism.languages[t]},{});t[e]={pattern:RegExp(/(<__[^>]*>)(?:))*\]\]>|(?!)/.source.replace(/__/g,function(){return e}),"i"),lookbehind:!0,greedy:!0,inside:n},Prism.languages.insertBefore("markup","cdata",t)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(e,t){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[t,"language-"+t],inside:Prism.languages[t]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml,function(e){var t=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/,t=(e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:"+/[^;{\s"']|\s+(?!\s)/.source+"|"+t.source+")*?"+/(?:;|(?=\s*\{))/.source),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+t.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+t.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+t.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:t,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css,e.languages.markup);t&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(Prism),Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp(/(^|[^\w$])/.source+"(?:"+/NaN|Infinity/.source+"|"+/0[bB][01]+(?:_[01]+)*n?/.source+"|"+/0[oO][0-7]+(?:_[0-7]+)*n?/.source+"|"+/0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source+"|"+/\d+(?:_\d+)*n/.source+"|"+/(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source+")"+/(?![\w$])/.source),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp(/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)/.source+/\//.source+"(?:"+/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}/.source+"|"+/(?:\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.)*\])*\])*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}v[dgimyus]{0,7}/.source+")"+/(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/.source),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),Prism.languages.js=Prism.languages.javascript,function(){var l,u,g,c,e;void 0!==Prism&&"undefined"!=typeof document&&(Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector),l={js:"javascript",py:"python",rb:"ruby",ps1:"powershell",psm1:"powershell",sh:"bash",bat:"batch",h:"c",tex:"latex"},c="pre[data-src]:not(["+(u="data-src-status")+'="loaded"]):not(['+u+'="'+(g="loading")+'"])',Prism.hooks.add("before-highlightall",function(e){e.selector+=", "+c}),Prism.hooks.add("before-sanity-check",function(e){var r,t,n,a,s,i,o=e.element;o.matches(c)&&(e.code="",o.setAttribute(u,g),(r=o.appendChild(document.createElement("CODE"))).textContent="Loading…",t=o.getAttribute("data-src"),"none"===(e=e.language)&&(n=(/\.(\w+)$/.exec(t)||[,"none"])[1],e=l[n]||n),Prism.util.setLanguage(r,e),Prism.util.setLanguage(o,e),(n=Prism.plugins.autoloader)&&n.loadLanguages(e),n=t,a=function(e){o.setAttribute(u,"loaded");var t,n,a=function(e){var t,n;if(e=/^\s*(\d+)\s*(?:(,)\s*(?:(\d+)\s*)?)?$/.exec(e||""))return t=Number(e[1]),n=e[2],e=e[3],n?e?[t,Number(e)]:[t,void 0]:[t,t]}(o.getAttribute("data-range"));a&&(t=e.split(/\r\n?|\n/g),n=a[0],a=null==a[1]?t.length:a[1],n<0&&(n+=t.length),n=Math.max(0,Math.min(n-1,t.length)),a<0&&(a+=t.length),a=Math.max(0,Math.min(a,t.length)),e=t.slice(n,a).join("\n"),o.hasAttribute("data-start")||o.setAttribute("data-start",String(n+1))),r.textContent=e,Prism.highlightElement(r)},s=function(e){o.setAttribute(u,"failed"),r.textContent=e},(i=new XMLHttpRequest).open("GET",n,!0),i.onreadystatechange=function(){4==i.readyState&&(i.status<400&&i.responseText?a(i.responseText):400<=i.status?s("✖ Error "+i.status+" while fetching file: "+i.statusText):s("✖ Error: File does not exist or is empty"))},i.send(null))}),e=!(Prism.plugins.fileHighlight={highlight:function(e){for(var t,n=(e||document).querySelectorAll(c),a=0;t=n[a++];)Prism.highlightElement(t)}}),Prism.fileHighlight=function(){e||(console.warn("Prism.fileHighlight is deprecated. Use `Prism.plugins.fileHighlight.highlight` instead."),e=!0),Prism.plugins.fileHighlight.highlight.apply(this,arguments)})}(); \ No newline at end of file diff --git a/build.py b/build.py index 5025f2c..9362505 100644 --- a/build.py +++ b/build.py @@ -142,6 +142,29 @@ def copy_cfg(output_dir: Path, cfg_name: str | None): log.info(f" Copying {cfg_name} databrowse depot...") target = output_dir / "station" / "monitors" / "databrowse" / "depot" copy_path(room_databrowse, target) + + # Copy room-specific tester tests if exists + room_tests = room_cfg / "tester" / "tests" + if room_tests.exists(): + log.info(f" Copying {cfg_name} tester tests...") + target = output_dir / "station" / "tools" / "tester" / "tests" + copy_path(room_tests, target) + + # Copy room-specific monitors if exists + room_monitors = room_cfg / "monitors" + if room_monitors.exists(): + log.info(f" Copying {cfg_name} monitors...") + for monitor in room_monitors.iterdir(): + if monitor.is_dir(): + target = output_dir / "station" / "monitors" / monitor.name + copy_path(monitor, target) + + # Copy room-specific models if exists + room_models = room_cfg / "models" + if room_models.exists(): + log.info(f" Copying {cfg_name} models...") + target = output_dir / "models" / cfg_name + copy_path(room_models, target) else: log.warning(f"Room config '{cfg_name}' not found at {room_cfg}") diff --git a/cfg/amar/models/__init__.py b/cfg/amar/models/__init__.py new file mode 100644 index 0000000..f39a9da --- /dev/null +++ b/cfg/amar/models/__init__.py @@ -0,0 +1,156 @@ +""" +Pawprint Models - Platform Agnostic Definitions + +Portable to: TypeScript, Pydantic, Django, SQLAlchemy, etc. + +Hierarchy: + pawprint (abstract) + ├── artery → Pulse = Vein + Nest + Larder + ├── album → Book = Template + Larder + └── ward → Table = Tools + Nest + Larder + +Shared components: Nest, Larder +System-specific: Vein (artery), Template (album), Tools (ward) + +Rules: + - Larder in album generated from Template = "Book (written)" + - Same Larder exists independently in ward/artery + - Nest contains runtime configs, credentials, targets + - Larder contains data, provisions, stored content +""" + +from dataclasses import dataclass, field +from typing import Optional, List +from enum import Enum + + +class Status(Enum): + PENDING = "pending" + PLANNED = "planned" + BUILDING = "building" + DEV = "dev" + LIVE = "live" + READY = "ready" + + +class System(Enum): + ARTERY = "artery" + ALBUM = "album" + WARD = "ward" + + +# === Shared Components === + +@dataclass +class Nest: + """Runtime environment configuration. + + Contains: credentials, targets, runtime configs. + Shared across: artery, ward + """ + name: str + status: Status = Status.PENDING + # References to actual config files/secrets + config_path: Optional[str] = None + + +@dataclass +class Larder: + """Data storage / provisions. + + Contains: data, transforms, parsers, dumps. + Shared across: artery, album, ward + + Note: When generated from Template in album, appears as "Book (written)" + but exists as independent Larder in ward/artery. + """ + name: str + status: Status = Status.PENDING + # Optional source template (if generated) + source_template: Optional[str] = None + # Path to data + data_path: Optional[str] = None + + +# === System-Specific Components === + +@dataclass +class Vein: + """Connector (artery-specific). + + Single responsibility data connector. + Examples: jira, google, slack, whatsapp, cash, vnc + """ + name: str + status: Status = Status.PENDING + system: System = field(default=System.ARTERY, init=False) + + +@dataclass +class Template: + """Documentation template (album-specific). + + Gherkin, BDD patterns, generators. + Examples: feature-form, gherkin + """ + name: str + status: Status = Status.PENDING + system: System = field(default=System.ALBUM, init=False) + + +@dataclass +class Tool: + """Execution tool (ward-specific). + + Test runners, seeders, scripts. + """ + name: str + status: Status = Status.PENDING + system: System = field(default=System.WARD, init=False) + + +# === Composed Types === + +@dataclass +class Pulse: + """Composed data flow (artery). + + Pulse = Vein + Nest + Larder + """ + name: str + status: Status = Status.PENDING + vein: Optional[Vein] = None + nest: Optional[Nest] = None + larder: Optional[Larder] = None + system: System = field(default=System.ARTERY, init=False) + + +@dataclass +class Book: + """Composed documentation (album). + + Book = Template + Larder + + Note: Output larder can be referenced independently in other systems. + """ + name: str + status: Status = Status.PENDING + template: Optional[Template] = None + larder: Optional[Larder] = None + # If this book produces a larder, it's tracked here + output_larder: Optional[Larder] = None + system: System = field(default=System.ALBUM, init=False) + + +@dataclass +class Table: + """Composed execution bundle (ward). + + Table = Tools + Nest + Larder + """ + name: str + status: Status = Status.PENDING + tools: List[Tool] = field(default_factory=list) + nest: Optional[Nest] = None + larder: Optional[Larder] = None + system: System = field(default=System.WARD, init=False) diff --git a/cfg/amar/models/django/models.py b/cfg/amar/models/django/models.py new file mode 100644 index 0000000..b2587a7 --- /dev/null +++ b/cfg/amar/models/django/models.py @@ -0,0 +1,191 @@ +""" +Django models - Generated from schema.json + +DO NOT EDIT MANUALLY - Regenerate from schema.json +""" + +from django.db import models + + +class Status(models.TextChoices): + PENDING = "pending", "Pending" + PLANNED = "planned", "Planned" + BUILDING = "building", "Building" + DEV = "dev", "Dev" + LIVE = "live", "Live" + READY = "ready", "Ready" + + +class System(models.TextChoices): + ARTERY = "artery", "Artery" + ALBUM = "album", "Album" + WARD = "ward", "Ward" + + +# === Shared Components === + +class Nest(models.Model): + """Runtime environment configuration. Shared across artery, ward.""" + name = models.CharField(max_length=255, help_text="Unique identifier") + slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier") + title = models.CharField(max_length=255, help_text="Display title for UI") + status = models.ForeignKey(Status, on_delete=models.SET_NULL, blank=True, null=True, related_name="nests") + config_path = models.CharField(max_length=255, blank=True, null=True) + + class Meta: + db_table = "pawprint_nest" + + def __str__(self): + return self.title + + +class Larder(models.Model): + """Data storage. When generated from Template = 'Book (written)'. Independent in ward/artery.""" + name = models.CharField(max_length=255, help_text="Unique identifier") + slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier") + title = models.CharField(max_length=255, help_text="Display title for UI") + status = models.ForeignKey(Status, on_delete=models.SET_NULL, blank=True, null=True, related_name="larders") + source_template = models.CharField(max_length=255, blank=True, null=True, help_text="Template name if generated") + data_path = models.CharField(max_length=255, blank=True, null=True, help_text="Path to data files") + + class Meta: + db_table = "pawprint_larder" + + def __str__(self): + return self.title + + +# === System-Specific Components === + +class Vein(models.Model): + """Connector (artery). Single responsibility.""" + name = models.CharField(max_length=255, help_text="Unique identifier") + slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier") + title = models.CharField(max_length=255, help_text="Display title for UI") + status = models.ForeignKey(Status, on_delete=models.SET_NULL, blank=True, null=True, related_name="veins") + system = models.CharField(max_length=20, default="artery", editable=False) + + class Meta: + db_table = "pawprint_vein" + + def __str__(self): + return self.title + + +class Template(models.Model): + """Documentation template (album). Gherkin, BDD patterns.""" + name = models.CharField(max_length=255, help_text="Unique identifier") + slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier") + title = models.CharField(max_length=255, help_text="Display title for UI") + status = models.ForeignKey(Status, on_delete=models.SET_NULL, blank=True, null=True, related_name="templates") + template_path = models.CharField(max_length=255, blank=True, null=True, help_text="Path to template files") + system = models.CharField(max_length=20, default="album", editable=False) + + class Meta: + db_table = "pawprint_template" + + def __str__(self): + return self.title + + +class Tool(models.Model): + """Execution tool (ward). Test runners, seeders.""" + name = models.CharField(max_length=255, help_text="Unique identifier") + slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier") + title = models.CharField(max_length=255, help_text="Display title for UI") + status = models.ForeignKey(Status, on_delete=models.SET_NULL, blank=True, null=True, related_name="tools") + system = models.CharField(max_length=20, default="ward", editable=False) + + class Meta: + db_table = "pawprint_tool" + + def __str__(self): + return self.title + + +class Monitor(models.Model): + """Service monitor (ward). Health checks, status watchers.""" + name = models.CharField(max_length=255, help_text="Unique identifier") + slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier") + title = models.CharField(max_length=255, help_text="Display title for UI") + status = models.ForeignKey(Status, on_delete=models.SET_NULL, blank=True, null=True, related_name="monitors") + system = models.CharField(max_length=20, default="ward", editable=False) + + class Meta: + db_table = "pawprint_monitor" + + def __str__(self): + return self.title + + +class Cabinet(models.Model): + """Tool cabinet (ward). Contains 0+ tools.""" + name = models.CharField(max_length=255, help_text="Unique identifier") + slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier") + title = models.CharField(max_length=255, help_text="Display title for UI") + status = models.ForeignKey(Status, on_delete=models.SET_NULL, blank=True, null=True, related_name="cabinets") + tools = models.ManyToManyField(Tool, blank=True) + system = models.CharField(max_length=20, default="ward", editable=False) + + class Meta: + db_table = "pawprint_cabinet" + + def __str__(self): + return self.title + + +# === Composed Types === + +class Pulse(models.Model): + """Composed data flow (artery). Pulse = Vein + Nest + Larder.""" + name = models.CharField(max_length=255, help_text="Unique identifier") + slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier") + title = models.CharField(max_length=255, help_text="Display title for UI") + status = models.ForeignKey(Status, on_delete=models.SET_NULL, blank=True, null=True, related_name="pulses") + vein = models.ForeignKey(Vein, on_delete=models.SET_NULL, blank=True, null=True, related_name="pulses") + nest = models.ForeignKey(Nest, on_delete=models.SET_NULL, blank=True, null=True, related_name="pulses") + larder = models.ForeignKey(Larder, on_delete=models.SET_NULL, blank=True, null=True, related_name="pulses") + system = models.CharField(max_length=20, default="artery", editable=False) + + class Meta: + db_table = "pawprint_pulse" + + def __str__(self): + return self.title + + +class Book(models.Model): + """Composed documentation (album). Book = Template + Larder.""" + name = models.CharField(max_length=255, help_text="Unique identifier") + slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier") + title = models.CharField(max_length=255, help_text="Display title for UI") + status = models.ForeignKey(Status, on_delete=models.SET_NULL, blank=True, null=True, related_name="books") + template = models.ForeignKey(Template, on_delete=models.SET_NULL, blank=True, null=True, related_name="books") + larder = models.ForeignKey(Larder, on_delete=models.SET_NULL, blank=True, null=True, related_name="books") + output_larder = models.ForeignKey(Larder, on_delete=models.SET_NULL, blank=True, null=True, related_name="books") + system = models.CharField(max_length=20, default="album", editable=False) + + class Meta: + db_table = "pawprint_book" + + def __str__(self): + return self.title + + +class Table(models.Model): + """Composed execution bundle (ward). Table = Cabinet + Nest + Larders.""" + name = models.CharField(max_length=255, help_text="Unique identifier") + slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier") + title = models.CharField(max_length=255, help_text="Display title for UI") + status = models.ForeignKey(Status, on_delete=models.SET_NULL, blank=True, null=True, related_name="tables") + cabinet = models.ForeignKey(Cabinet, on_delete=models.SET_NULL, blank=True, null=True, related_name="tables") + nest = models.ForeignKey(Nest, on_delete=models.SET_NULL, blank=True, null=True, related_name="tables") + larders = models.ManyToManyField(Larder, blank=True) + system = models.CharField(max_length=20, default="ward", editable=False) + + class Meta: + db_table = "pawprint_table" + + def __str__(self): + return self.title + diff --git a/cfg/amar/models/prisma/schema.prisma b/cfg/amar/models/prisma/schema.prisma new file mode 100644 index 0000000..4c7a8c6 --- /dev/null +++ b/cfg/amar/models/prisma/schema.prisma @@ -0,0 +1,213 @@ +// Prisma schema - Generated from schema.json +// +// DO NOT EDIT MANUALLY - Regenerate from schema.json + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +enum Status { + PENDING + PLANNED + BUILDING + DEV + LIVE + READY +} + +enum System { + ARTERY + ALBUM + WARD +} + +// === Shared Components === + +/// Runtime environment configuration. Shared across artery, ward. +model Nest { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + name String @unique + slug String @unique + title String + status Status? @relation(fields: [statusId], references: [id]) + statusId Int? + config_path String? + + @@map("pawprint_nest") +} + +/// Data storage. When generated from Template = 'Book (written)'. Independent in ward/artery. +model Larder { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + name String @unique + slug String @unique + title String + status Status? @relation(fields: [statusId], references: [id]) + statusId Int? + source_template String? + data_path String? + + @@map("pawprint_larder") +} + +// === System-Specific Components === + +/// Connector (artery). Single responsibility. +model Vein { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + name String @unique + slug String @unique + title String + status Status? @relation(fields: [statusId], references: [id]) + statusId Int? + system String @default("artery") + + @@map("pawprint_vein") +} + +/// Documentation template (album). Gherkin, BDD patterns. +model Template { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + name String @unique + slug String @unique + title String + status Status? @relation(fields: [statusId], references: [id]) + statusId Int? + template_path String? + system String @default("album") + + @@map("pawprint_template") +} + +/// Execution tool (ward). Test runners, seeders. +model Tool { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + name String @unique + slug String @unique + title String + status Status? @relation(fields: [statusId], references: [id]) + statusId Int? + system String @default("ward") + + @@map("pawprint_tool") +} + +/// Service monitor (ward). Health checks, status watchers. +model Monitor { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + name String @unique + slug String @unique + title String + status Status? @relation(fields: [statusId], references: [id]) + statusId Int? + system String @default("ward") + + @@map("pawprint_monitor") +} + +/// Tool cabinet (ward). Contains 0+ tools. +model Cabinet { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + name String @unique + slug String @unique + title String + status Status? @relation(fields: [statusId], references: [id]) + statusId Int? + tools Tool[] + system String @default("ward") + + @@map("pawprint_cabinet") +} + +// === Composed Types === + +/// Composed data flow (artery). Pulse = Vein + Nest + Larder. +model Pulse { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + name String @unique + slug String @unique + title String + status Status? @relation(fields: [statusId], references: [id]) + statusId Int? + vein Vein? @relation(fields: [veinId], references: [id]) + veinId Int? + nest Nest? @relation(fields: [nestId], references: [id]) + nestId Int? + larder Larder? @relation(fields: [larderId], references: [id]) + larderId Int? + system String @default("artery") + + @@map("pawprint_pulse") +} + +/// Composed documentation (album). Book = Template + Larder. +model Book { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + name String @unique + slug String @unique + title String + status Status? @relation(fields: [statusId], references: [id]) + statusId Int? + template Template? @relation(fields: [templateId], references: [id]) + templateId Int? + larder Larder? @relation(fields: [larderId], references: [id]) + larderId Int? + output_larder Larder? @relation(fields: [output_larderId], references: [id]) + output_larderId Int? + system String @default("album") + + @@map("pawprint_book") +} + +/// Composed execution bundle (ward). Table = Cabinet + Nest + Larders. +model Table { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + name String @unique + slug String @unique + title String + status Status? @relation(fields: [statusId], references: [id]) + statusId Int? + cabinet Cabinet? @relation(fields: [cabinetId], references: [id]) + cabinetId Int? + nest Nest? @relation(fields: [nestId], references: [id]) + nestId Int? + larders Larder[] + system String @default("ward") + + @@map("pawprint_table") +} diff --git a/cfg/amar/models/pydantic/__init__.py b/cfg/amar/models/pydantic/__init__.py new file mode 100644 index 0000000..378e0ab --- /dev/null +++ b/cfg/amar/models/pydantic/__init__.py @@ -0,0 +1,187 @@ +""" +Pydantic models - Generated from schema.json + +DO NOT EDIT MANUALLY - Regenerate from schema.json +""" + +from enum import Enum +from typing import Optional, List, Literal +from pydantic import BaseModel, Field + + +class Status(str, Enum): + PENDING = "pending" + PLANNED = "planned" + BUILDING = "building" + DEV = "dev" + LIVE = "live" + READY = "ready" + + +class System(str, Enum): + ARTERY = "artery" + ALBUM = "album" + WARD = "ward" + + +class ToolType(str, Enum): + APP = "app" + CLI = "cli" + + +# === Shared Components === + +class Nest(BaseModel): + """Runtime environment configuration. Shared across artery, ward.""" + name: str # Unique identifier + slug: str # URL-friendly identifier + title: str # Display title for UI + status: Optional[Status] = None + config_path: Optional[str] = None + + +class Larder(BaseModel): + """Data storage. When generated from Template = 'Book (written)'. Independent in ward/artery.""" + name: str # Unique identifier + slug: str # URL-friendly identifier + title: str # Display title for UI + status: Optional[Status] = None + source_template: Optional[str] = None + data_path: Optional[str] = None + + +# === System-Specific Components === + +class Vein(BaseModel): + """Connector (artery). Single responsibility.""" + name: str # Unique identifier + slug: str # URL-friendly identifier + title: str # Display title for UI + status: Optional[Status] = None + system: Literal["artery"] = "artery" + mock: Optional[bool] = None + description: Optional[str] = None + + +class Template(BaseModel): + """Documentation template (album). Gherkin, BDD patterns.""" + name: str # Unique identifier + slug: str # URL-friendly identifier + title: str # Display title for UI + status: Optional[Status] = None + template_path: Optional[str] = None + system: Literal["album"] = "album" + + +class Tool(BaseModel): + """Execution tool (ward). Test runners, seeders.""" + name: str # Unique identifier + slug: str # URL-friendly identifier + title: str # Display title for UI + status: Optional[Status] = None + system: Literal["ward"] = "ward" + type: Optional[ToolType] = None + description: Optional[str] = None + path: Optional[str] = None + url: Optional[str] = None + cli: Optional[str] = None + + +class Monitor(BaseModel): + """Service monitor (ward). Health checks, status watchers.""" + name: str # Unique identifier + slug: str # URL-friendly identifier + title: str # Display title for UI + status: Optional[Status] = None + system: Literal["ward"] = "ward" + + +class Cabinet(BaseModel): + """Tool cabinet (ward). Contains 0+ tools.""" + name: str # Unique identifier + slug: str # URL-friendly identifier + title: str # Display title for UI + status: Optional[Status] = None + tools: List[Tool] = Field(default_factory=list) + system: Literal["ward"] = "ward" + + +# === Composed Types === + +class Pulse(BaseModel): + """Composed data flow (artery). Pulse = Vein + Nest + Larder.""" + name: str # Unique identifier + slug: str # URL-friendly identifier + title: str # Display title for UI + status: Optional[Status] = None + vein: Optional[Vein] = None + nest: Optional[Nest] = None + larder: Optional[Larder] = None + system: Literal["artery"] = "artery" + + +class Book(BaseModel): + """Composed documentation (album). Book = Template + Larder.""" + name: str # Unique identifier + slug: str # URL-friendly identifier + title: str # Display title for UI + status: Optional[Status] = None + template: Optional[Template] = None + larder: Optional[Larder] = None + output_larder: Optional[Larder] = None + system: Literal["album"] = "album" + + +class Table(BaseModel): + """Composed execution bundle (ward). Table = Cabinet + Nest + Larders.""" + name: str # Unique identifier + slug: str # URL-friendly identifier + title: str # Display title for UI + status: Optional[Status] = None + cabinet: Optional[Cabinet] = None + nest: Optional[Nest] = None + larders: List[Larder] = Field(default_factory=list) + system: Literal["ward"] = "ward" + + +# === Collection wrappers for JSON files === + +class NestCollection(BaseModel): + items: List[Nest] = Field(default_factory=list) + + +class LarderCollection(BaseModel): + items: List[Larder] = Field(default_factory=list) + + +class VeinCollection(BaseModel): + items: List[Vein] = Field(default_factory=list) + + +class TemplateCollection(BaseModel): + items: List[Template] = Field(default_factory=list) + + +class ToolCollection(BaseModel): + items: List[Tool] = Field(default_factory=list) + + +class MonitorCollection(BaseModel): + items: List[Monitor] = Field(default_factory=list) + + +class CabinetCollection(BaseModel): + items: List[Cabinet] = Field(default_factory=list) + + +class PulseCollection(BaseModel): + items: List[Pulse] = Field(default_factory=list) + + +class BookCollection(BaseModel): + items: List[Book] = Field(default_factory=list) + + +class TableCollection(BaseModel): + items: List[Table] = Field(default_factory=list) + diff --git a/cfg/amar/models/schema.json b/cfg/amar/models/schema.json new file mode 100644 index 0000000..d7bcb20 --- /dev/null +++ b/cfg/amar/models/schema.json @@ -0,0 +1,163 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Pawprint Models", + "description": "Platform-agnostic model definitions. Portable to TypeScript, Pydantic, Django, Prisma.", + "definitions": { + "Status": { + "type": "string", + "enum": ["pending", "planned", "building", "dev", "live", "ready"] + }, + "System": { + "type": "string", + "enum": ["artery", "album", "ward"] + }, + "Nest": { + "type": "object", + "description": "Runtime environment configuration. Shared across artery, ward.", + "properties": { + "name": { "type": "string", "description": "Unique identifier" }, + "slug": { "type": "string", "description": "URL-friendly identifier" }, + "title": { "type": "string", "description": "Display title for UI" }, + "status": { "$ref": "#/definitions/Status" }, + "config_path": { "type": "string" } + }, + "required": ["name", "slug", "title"] + }, + "Larder": { + "type": "object", + "description": "Data storage. When generated from Template = 'Book (written)'. Independent in ward/artery.", + "properties": { + "name": { "type": "string", "description": "Unique identifier" }, + "slug": { "type": "string", "description": "URL-friendly identifier" }, + "title": { "type": "string", "description": "Display title for UI" }, + "status": { "$ref": "#/definitions/Status" }, + "source_template": { "type": "string", "description": "Template name if generated" }, + "data_path": { "type": "string", "description": "Path to data files" } + }, + "required": ["name", "slug", "title"] + }, + "Vein": { + "type": "object", + "description": "Connector (artery). Single responsibility.", + "properties": { + "name": { "type": "string", "description": "Unique identifier" }, + "slug": { "type": "string", "description": "URL-friendly identifier" }, + "title": { "type": "string", "description": "Display title for UI" }, + "status": { "$ref": "#/definitions/Status" }, + "system": { "const": "artery" } + }, + "required": ["name", "slug", "title"] + }, + "Template": { + "type": "object", + "description": "Documentation template (album). Gherkin, BDD patterns.", + "properties": { + "name": { "type": "string", "description": "Unique identifier" }, + "slug": { "type": "string", "description": "URL-friendly identifier" }, + "title": { "type": "string", "description": "Display title for UI" }, + "status": { "$ref": "#/definitions/Status" }, + "template_path": { "type": "string", "description": "Path to template files" }, + "system": { "const": "album" } + }, + "required": ["name", "slug", "title"] + }, + "ToolType": { + "type": "string", + "enum": ["app", "cli"], + "description": "Type of tool: app (web UI) or cli (command line)" + }, + "Tool": { + "type": "object", + "description": "Execution tool (ward). Test runners, seeders.", + "properties": { + "name": { "type": "string", "description": "Unique identifier" }, + "slug": { "type": "string", "description": "URL-friendly identifier" }, + "title": { "type": "string", "description": "Display title for UI" }, + "status": { "$ref": "#/definitions/Status" }, + "system": { "const": "ward" }, + "type": { "$ref": "#/definitions/ToolType" }, + "description": { "type": "string", "description": "Human-readable description" }, + "path": { "type": "string", "description": "Path to tool source" }, + "url": { "type": "string", "description": "URL path for app tools" }, + "cli": { "type": "string", "description": "CLI command for cli tools" } + }, + "required": ["name", "slug", "title"] + }, + "Monitor": { + "type": "object", + "description": "Service monitor (ward). Health checks, status watchers.", + "properties": { + "name": { "type": "string", "description": "Unique identifier" }, + "slug": { "type": "string", "description": "URL-friendly identifier" }, + "title": { "type": "string", "description": "Display title for UI" }, + "status": { "$ref": "#/definitions/Status" }, + "system": { "const": "ward" } + }, + "required": ["name", "slug", "title"] + }, + "Cabinet": { + "type": "object", + "description": "Tool cabinet (ward). Contains 0+ tools.", + "properties": { + "name": { "type": "string", "description": "Unique identifier" }, + "slug": { "type": "string", "description": "URL-friendly identifier" }, + "title": { "type": "string", "description": "Display title for UI" }, + "status": { "$ref": "#/definitions/Status" }, + "tools": { + "type": "array", + "items": { "$ref": "#/definitions/Tool" } + }, + "system": { "const": "ward" } + }, + "required": ["name", "slug", "title"] + }, + "Pulse": { + "type": "object", + "description": "Composed data flow (artery). Pulse = Vein + Nest + Larder.", + "properties": { + "name": { "type": "string", "description": "Unique identifier" }, + "slug": { "type": "string", "description": "URL-friendly identifier" }, + "title": { "type": "string", "description": "Display title for UI" }, + "status": { "$ref": "#/definitions/Status" }, + "vein": { "$ref": "#/definitions/Vein" }, + "nest": { "$ref": "#/definitions/Nest" }, + "larder": { "$ref": "#/definitions/Larder" }, + "system": { "const": "artery" } + }, + "required": ["name", "slug", "title"] + }, + "Book": { + "type": "object", + "description": "Composed documentation (album). Book = Template + Larder.", + "properties": { + "name": { "type": "string", "description": "Unique identifier" }, + "slug": { "type": "string", "description": "URL-friendly identifier" }, + "title": { "type": "string", "description": "Display title for UI" }, + "status": { "$ref": "#/definitions/Status" }, + "template": { "$ref": "#/definitions/Template" }, + "larder": { "$ref": "#/definitions/Larder" }, + "output_larder": { "$ref": "#/definitions/Larder" }, + "system": { "const": "album" } + }, + "required": ["name", "slug", "title"] + }, + "Table": { + "type": "object", + "description": "Composed execution bundle (ward). Table = Cabinet + Nest + Larders.", + "properties": { + "name": { "type": "string", "description": "Unique identifier" }, + "slug": { "type": "string", "description": "URL-friendly identifier" }, + "title": { "type": "string", "description": "Display title for UI" }, + "status": { "$ref": "#/definitions/Status" }, + "cabinet": { "$ref": "#/definitions/Cabinet" }, + "nest": { "$ref": "#/definitions/Nest" }, + "larders": { + "type": "array", + "items": { "$ref": "#/definitions/Larder" } + }, + "system": { "const": "ward" } + }, + "required": ["name", "slug", "title"] + } + } +} diff --git a/cfg/amar/monitors/turnos/__init__.py b/cfg/amar/monitors/turnos/__init__.py new file mode 100644 index 0000000..15ea7cc --- /dev/null +++ b/cfg/amar/monitors/turnos/__init__.py @@ -0,0 +1,6 @@ +""" +Turnos Monitor - At-a-glance view of request/turno pipeline. + +Shows the flow: Request -> Vet Accept -> Payment -> Turno +Color-coded by state, minimal info (vet - pet owner). +""" diff --git a/cfg/amar/monitors/turnos/index.html b/cfg/amar/monitors/turnos/index.html new file mode 100644 index 0000000..11907fe --- /dev/null +++ b/cfg/amar/monitors/turnos/index.html @@ -0,0 +1,244 @@ + + + + + + + Turnos · {{ nest_name }} + + + +
+

+ Turnos + {{ nest_name }} +

+
+
+ Pipeline + List +
+
+ {{ total }} + activos +
+
+
+ + {% if total == 0 %} +
+ No hay solicitudes activas +
+ {% else %} +
+ {% for state in active_states %} + {% set info = states.get(state, ('?', '#888', 99)) %} + {% set items = by_state.get(state, []) %} +
+
+ {{ info[0] }} + {{ items|length }} +
+
+ {% for item in items %} +
+
#{{ item.id }}
+
{{ item.vet }}
+
{{ item.petowner }}
+
+ {% if item.is_paid %}{% endif %} + {% if item.is_scheduled %}{% endif %} + {{ item.age_hours }}h +
+
+ {% endfor %} +
+
+ {% endfor %} +
+ {% endif %} + + + + diff --git a/cfg/amar/monitors/turnos/list.html b/cfg/amar/monitors/turnos/list.html new file mode 100644 index 0000000..096a7ed --- /dev/null +++ b/cfg/amar/monitors/turnos/list.html @@ -0,0 +1,188 @@ + + + + + + + Turnos List · {{ nest_name }} + + + +
+

+ Turnos + {{ nest_name }} +

+
+
+ Pipeline + List +
+
{{ total }}
+
+
+ + {% if total == 0 %} +
No hay solicitudes activas
+ {% else %} + + + + + + + + + + + + + {% for item in items %} + + + + + + + + + {% endfor %} + +
#EstadoVeterinarioClienteFlagsEdad
{{ item.id }} + + {{ item.state_label }} + + {{ item.vet }}{{ item.petowner }} + {% if item.is_paid %}{% endif %} + {% if item.is_scheduled %}{% endif %} + {{ item.age_hours }}h
+ {% endif %} + + + + diff --git a/cfg/amar/monitors/turnos/main.py b/cfg/amar/monitors/turnos/main.py new file mode 100644 index 0000000..5a2ad31 --- /dev/null +++ b/cfg/amar/monitors/turnos/main.py @@ -0,0 +1,270 @@ +""" +Turnos Monitor - At-a-glance view of the request/turno pipeline. + +Request -> Vet Accept + Payment -> Turno (VetVisit) + +Run standalone: + python -m ward.tools.monitors.turnos.main + +Or use uvicorn: + uvicorn ward.tools.monitors.turnos.main:app --port 12010 --reload +""" + +import os +from datetime import datetime +from pathlib import Path +from typing import Optional + +from fastapi import FastAPI, Request +from fastapi.responses import HTMLResponse +from fastapi.templating import Jinja2Templates +from sqlalchemy import create_engine, text +from sqlalchemy.engine import Engine + +app = FastAPI(title="Turnos Monitor", version="0.1.0") + +templates = Jinja2Templates(directory=Path(__file__).parent) + +# ============================================================================= +# NEST CONFIG - Pluggable environment targeting +# ============================================================================= +# Default nest: local development database +# Override with env vars or future nest selector UI + +NEST_CONFIG = { + "name": os.getenv("NEST_NAME", "local"), + "db": { + "host": os.getenv("DB_HOST", "localhost"), + "port": os.getenv("DB_PORT", "5433"), # local default + "name": os.getenv("DB_NAME", "amarback"), + "user": os.getenv("DB_USER", "mariano"), + "password": os.getenv("DB_PASSWORD", ""), + } +} + + +def get_db_url() -> str: + """Build database URL from nest config.""" + db = NEST_CONFIG["db"] + return f"postgresql://{db['user']}:{db['password']}@{db['host']}:{db['port']}/{db['name']}" + + +# ============================================================================= +# STATE DEFINITIONS +# ============================================================================= +# Pipeline states with labels, colors, and order + +STATES = { + "pending": ("Sin Atender", "#fbbf24", 1), # amber + "in_progress_vet": ("Buscando Vet", "#f97316", 2), # orange + "vet_asked": ("Esperando Vet", "#fb923c", 3), # orange light + "vet_accepted": ("Vet OK", "#4ade80", 4), # green + "in_progress_pay": ("Esp. Pago", "#60a5fa", 5), # blue + "payed": ("Pagado", "#2dd4bf", 6), # teal + "coordinated": ("Coordinado", "#22c55e", 7), # green + "not_coordinated": ("Sin Coord.", "#facc15", 8), # yellow + "completed": ("Turno", "#059669", 9), # emerald + "rejected": ("Rechazado", "#f87171", 10), # red +} + +# States to show in the active pipeline (exclude end states) +ACTIVE_STATES = [ + "pending", + "in_progress_vet", + "vet_asked", + "vet_accepted", + "in_progress_pay", + "payed", + "not_coordinated", + "coordinated", +] + +# ============================================================================= +# DATABASE +# ============================================================================= + +_engine: Optional[Engine] = None + + +def get_engine() -> Optional[Engine]: + """Get or create database engine (lazy singleton).""" + global _engine + if _engine is None: + try: + _engine = create_engine(get_db_url(), pool_pre_ping=True) + except Exception as e: + print(f"[turnos] DB engine error: {e}") + return _engine + + +def fetch_active_requests() -> list[dict]: + """Fetch active service requests with vet and petowner info.""" + engine = get_engine() + if not engine: + return [] + + query = text(""" + SELECT + sr.id, + sr.state, + sr.created_at, + sr.date_coordinated, + sr.hour_coordinated, + sr.pay_number, + po.first_name || ' ' || COALESCE(po.last_name, '') as petowner_name, + COALESCE(v.first_name || ' ' || v.last_name, '') as vet_name + FROM solicitudes_servicerequest sr + JOIN mascotas_petowner po ON sr.petowner_id = po.id + LEFT JOIN mascotas_veterinarian v ON sr.veterinarian_id = v.id + WHERE sr.state = ANY(:states) + ORDER BY + CASE sr.state + WHEN 'pending' THEN 1 + WHEN 'in_progress_vet' THEN 2 + WHEN 'vet_asked' THEN 3 + WHEN 'vet_accepted' THEN 4 + WHEN 'in_progress_pay' THEN 5 + WHEN 'payed' THEN 6 + WHEN 'not_coordinated' THEN 7 + WHEN 'coordinated' THEN 8 + ELSE 9 + END, + sr.created_at DESC + """) + + try: + with engine.connect() as conn: + result = conn.execute(query, {"states": ACTIVE_STATES}) + rows = result.fetchall() + + requests = [] + for row in rows: + state_info = STATES.get(row.state, ("?", "#888", 99)) + age_h = _hours_since(row.created_at) + + requests.append({ + "id": row.id, + "state": row.state, + "state_label": state_info[0], + "state_color": state_info[1], + "petowner": row.petowner_name.strip(), + "vet": row.vet_name.strip() if row.vet_name else "-", + "is_paid": bool(row.pay_number), + "is_scheduled": bool(row.date_coordinated and row.hour_coordinated), + "age_hours": age_h, + "age_class": "old" if age_h > 48 else ("warn" if age_h > 24 else ""), + }) + return requests + + except Exception as e: + print(f"[turnos] Query error: {e}") + return [] + + +def fetch_counts() -> dict[str, int]: + """Fetch count per state.""" + engine = get_engine() + if not engine: + return {} + + query = text(""" + SELECT state, COUNT(*) as cnt + FROM solicitudes_servicerequest + WHERE state = ANY(:states) + GROUP BY state + """) + + try: + with engine.connect() as conn: + result = conn.execute(query, {"states": ACTIVE_STATES}) + return {row.state: row.cnt for row in result.fetchall()} + except Exception as e: + print(f"[turnos] Count error: {e}") + return {} + + +def _hours_since(dt: Optional[datetime]) -> int: + """Hours since datetime.""" + if not dt: + return 0 + try: + now = datetime.now(dt.tzinfo) if dt.tzinfo else datetime.now() + return int((now - dt).total_seconds() / 3600) + except: + return 0 + + +# ============================================================================= +# ROUTES +# ============================================================================= + +@app.get("/health") +def health(): + """Health check.""" + engine = get_engine() + db_ok = False + if engine: + try: + with engine.connect() as conn: + conn.execute(text("SELECT 1")) + db_ok = True + except: + pass + + return { + "status": "ok" if db_ok else "degraded", + "service": "turnos-monitor", + "nest": NEST_CONFIG["name"], + "database": "connected" if db_ok else "disconnected", + } + + +@app.get("/", response_class=HTMLResponse) +def index(request: Request, view: str = "pipeline"): + """Main monitor view. ?view=list for list view.""" + requests_data = fetch_active_requests() + counts = fetch_counts() + + # Group by state + by_state = {s: [] for s in ACTIVE_STATES} + for req in requests_data: + if req["state"] in by_state: + by_state[req["state"]].append(req) + + template = "list.html" if view == "list" else "index.html" + + return templates.TemplateResponse(template, { + "request": request, + "items": requests_data, + "by_state": by_state, + "counts": counts, + "states": STATES, + "active_states": ACTIVE_STATES, + "total": len(requests_data), + "nest_name": NEST_CONFIG["name"], + "view": view, + }) + + +@app.get("/api/data") +def api_data(): + """JSON API endpoint.""" + return { + "nest": NEST_CONFIG["name"], + "requests": fetch_active_requests(), + "counts": fetch_counts(), + } + + +# ============================================================================= +# MAIN +# ============================================================================= + +if __name__ == "__main__": + import uvicorn + uvicorn.run( + "main:app", + host="0.0.0.0", + port=int(os.getenv("PORT", "12010")), + reload=True, + ) diff --git a/cfg/amar/tester/tests/README.md b/cfg/amar/tester/tests/README.md new file mode 100644 index 0000000..19a6871 --- /dev/null +++ b/cfg/amar/tester/tests/README.md @@ -0,0 +1,73 @@ +# Contract Tests + +API contract tests organized by Django app, with optional workflow tests. + +## Testing Modes + +Two modes via `CONTRACT_TEST_MODE` environment variable: + +| Mode | Command | Description | +|------|---------|-------------| +| **api** (default) | `pytest tests/contracts/` | Fast, Django test client, test DB | +| **live** | `CONTRACT_TEST_MODE=live pytest tests/contracts/` | Real HTTP, LiveServerTestCase, test DB | + +### Mode Comparison + +| | `api` (default) | `live` | +|---|---|---| +| **Base class** | `APITestCase` | `LiveServerTestCase` | +| **HTTP** | In-process (Django test client) | Real HTTP via `requests` | +| **Auth** | `force_authenticate()` | JWT tokens via API | +| **Database** | Django test DB (isolated) | Django test DB (isolated) | +| **Speed** | ~3-5 sec | ~15-30 sec | +| **Server** | None (in-process) | Auto-started by Django | + +### Key Point: Both Modes Use Test Database + +Neither mode touches your real database. Django automatically: +1. Creates a test database (prefixed with `test_`) +2. Runs migrations +3. Destroys it after tests complete + +## File Structure + +``` +tests/contracts/ +├── base.py # Mode switcher (imports from base_api or base_live) +├── base_api.py # APITestCase implementation +├── base_live.py # LiveServerTestCase implementation +├── conftest.py # pytest-django configuration +├── endpoints.py # API paths (single source of truth) +├── helpers.py # Shared test data helpers +│ +├── mascotas/ # Django app: mascotas +│ ├── test_pet_owners.py +│ ├── test_pets.py +│ └── test_coverage.py +│ +├── productos/ # Django app: productos +│ ├── test_services.py +│ └── test_cart.py +│ +├── solicitudes/ # Django app: solicitudes +│ └── test_service_requests.py +│ +└── workflows/ # Multi-step API sequences (e.g., turnero booking flow) + └── test_turnero_general.py +``` + +## Running Tests + +```bash +# All contract tests +pytest tests/contracts/ + +# Single app +pytest tests/contracts/mascotas/ + +# Single file +pytest tests/contracts/mascotas/test_pet_owners.py + +# Live mode (real HTTP) +CONTRACT_TEST_MODE=live pytest tests/contracts/ +``` diff --git a/cfg/amar/tester/tests/__init__.py b/cfg/amar/tester/tests/__init__.py new file mode 100644 index 0000000..9abad81 --- /dev/null +++ b/cfg/amar/tester/tests/__init__.py @@ -0,0 +1,2 @@ +# Contract tests - black-box HTTP tests that validate API contracts +# These tests are decoupled from Django and can run against any implementation diff --git a/cfg/amar/tester/tests/base.py b/cfg/amar/tester/tests/base.py new file mode 100644 index 0000000..3120c06 --- /dev/null +++ b/cfg/amar/tester/tests/base.py @@ -0,0 +1,164 @@ +""" +Pure HTTP Contract Tests - Base Class + +Framework-agnostic: works against ANY backend implementation. +Does NOT manage database - expects a ready environment. + +Requirements: + - Server running at CONTRACT_TEST_URL + - Database migrated and seeded + - Test user exists OR CONTRACT_TEST_TOKEN provided + +Usage: + CONTRACT_TEST_URL=http://127.0.0.1:8000 pytest + CONTRACT_TEST_TOKEN=your_jwt_token pytest +""" + +import os +import unittest +import httpx + +from .endpoints import Endpoints + + +def get_base_url(): + """Get base URL from environment (required)""" + url = os.environ.get("CONTRACT_TEST_URL", "") + if not url: + raise ValueError("CONTRACT_TEST_URL environment variable required") + return url.rstrip("/") + + +class ContractTestCase(unittest.TestCase): + """ + Base class for pure HTTP contract tests. + + Features: + - Framework-agnostic (works with Django, FastAPI, Node, etc.) + - Pure HTTP via requests library + - No database access - all data through API + - JWT authentication + """ + + # Auth credentials - override via environment + TEST_USER_EMAIL = os.environ.get("CONTRACT_TEST_USER", "contract_test@example.com") + TEST_USER_PASSWORD = os.environ.get("CONTRACT_TEST_PASSWORD", "testpass123") + + # Class-level cache + _base_url = None + _token = None + + @classmethod + def setUpClass(cls): + """Set up once per test class""" + super().setUpClass() + cls._base_url = get_base_url() + + # Use provided token or fetch one + cls._token = os.environ.get("CONTRACT_TEST_TOKEN", "") + if not cls._token: + cls._token = cls._fetch_token() + + @classmethod + def _fetch_token(cls): + """Get JWT token for authentication""" + url = f"{cls._base_url}{Endpoints.TOKEN}" + try: + response = httpx.post(url, json={ + "username": cls.TEST_USER_EMAIL, + "password": cls.TEST_USER_PASSWORD, + }, timeout=10) + if response.status_code == 200: + return response.json().get("access", "") + else: + print(f"Warning: Token request failed with {response.status_code}") + except httpx.RequestError as e: + print(f"Warning: Token request failed: {e}") + return "" + + @property + def base_url(self): + return self._base_url + + @property + def token(self): + return self._token + + def _auth_headers(self): + """Get authorization headers""" + if self.token: + return {"Authorization": f"Bearer {self.token}"} + return {} + + # ========================================================================= + # HTTP helpers + # ========================================================================= + + def get(self, path: str, params: dict = None, **kwargs): + """GET request""" + url = f"{self.base_url}{path}" + headers = {**self._auth_headers(), **kwargs.pop("headers", {})} + response = httpx.get(url, params=params, headers=headers, timeout=30, **kwargs) + return self._wrap_response(response) + + def post(self, path: str, data: dict = None, **kwargs): + """POST request with JSON""" + url = f"{self.base_url}{path}" + headers = {**self._auth_headers(), **kwargs.pop("headers", {})} + response = httpx.post(url, json=data, headers=headers, timeout=30, **kwargs) + return self._wrap_response(response) + + def put(self, path: str, data: dict = None, **kwargs): + """PUT request with JSON""" + url = f"{self.base_url}{path}" + headers = {**self._auth_headers(), **kwargs.pop("headers", {})} + response = httpx.put(url, json=data, headers=headers, timeout=30, **kwargs) + return self._wrap_response(response) + + def patch(self, path: str, data: dict = None, **kwargs): + """PATCH request with JSON""" + url = f"{self.base_url}{path}" + headers = {**self._auth_headers(), **kwargs.pop("headers", {})} + response = httpx.patch(url, json=data, headers=headers, timeout=30, **kwargs) + return self._wrap_response(response) + + def delete(self, path: str, **kwargs): + """DELETE request""" + url = f"{self.base_url}{path}" + headers = {**self._auth_headers(), **kwargs.pop("headers", {})} + response = httpx.delete(url, headers=headers, timeout=30, **kwargs) + return self._wrap_response(response) + + def _wrap_response(self, response): + """Add .data attribute for consistency with DRF responses""" + try: + response.data = response.json() + except Exception: + response.data = None + return response + + # ========================================================================= + # Assertion helpers + # ========================================================================= + + def assert_status(self, response, expected_status: int): + """Assert response has expected status code""" + self.assertEqual( + response.status_code, + expected_status, + f"Expected {expected_status}, got {response.status_code}. " + f"Response: {response.data if hasattr(response, 'data') else response.content[:500]}" + ) + + def assert_has_fields(self, data: dict, *fields: str): + """Assert dictionary has all specified fields""" + missing = [f for f in fields if f not in data] + self.assertEqual(missing, [], f"Missing fields: {missing}. Got: {list(data.keys())}") + + def assert_is_list(self, data, min_length: int = 0): + """Assert data is a list with minimum length""" + self.assertIsInstance(data, list) + self.assertGreaterEqual(len(data), min_length) + + +__all__ = ["ContractTestCase"] diff --git a/cfg/amar/tester/tests/conftest.py b/cfg/amar/tester/tests/conftest.py new file mode 100644 index 0000000..cfbc6dd --- /dev/null +++ b/cfg/amar/tester/tests/conftest.py @@ -0,0 +1,29 @@ +""" +Contract Tests Configuration + +Supports two testing modes via CONTRACT_TEST_MODE environment variable: + + # Fast mode (default) - Django test client, test DB + pytest tests/contracts/ + + # Live mode - Real HTTP with LiveServerTestCase, test DB + CONTRACT_TEST_MODE=live pytest tests/contracts/ +""" + +import os +import pytest + +# Let pytest-django handle Django setup via pytest.ini DJANGO_SETTINGS_MODULE + + +def pytest_configure(config): + """Register custom markers""" + config.addinivalue_line( + "markers", "workflow: marks test as a workflow/flow test (runs endpoint tests in sequence)" + ) + + +@pytest.fixture(scope="session") +def contract_test_mode(): + """Return current test mode""" + return os.environ.get("CONTRACT_TEST_MODE", "api") diff --git a/cfg/amar/tester/tests/endpoints.py b/cfg/amar/tester/tests/endpoints.py new file mode 100644 index 0000000..7af2031 --- /dev/null +++ b/cfg/amar/tester/tests/endpoints.py @@ -0,0 +1,38 @@ +""" +API Endpoints - Single source of truth for contract tests. + +If API paths or versioning changes, update here only. +""" + + +class Endpoints: + """API endpoint paths""" + + # ========================================================================== + # Mascotas + # ========================================================================== + PET_OWNERS = "/mascotas/api/v1/pet-owners/" + PET_OWNER_DETAIL = "/mascotas/api/v1/pet-owners/{id}/" + PETS = "/mascotas/api/v1/pets/" + PET_DETAIL = "/mascotas/api/v1/pets/{id}/" + COVERAGE_CHECK = "/mascotas/api/v1/coverage/check/" + + # ========================================================================== + # Productos + # ========================================================================== + SERVICES = "/productos/api/v1/services/" + CATEGORIES = "/productos/api/v1/categories/" + CART = "/productos/api/v1/cart/" + CART_DETAIL = "/productos/api/v1/cart/{id}/" + + # ========================================================================== + # Solicitudes + # ========================================================================== + SERVICE_REQUESTS = "/solicitudes/service-requests/" + SERVICE_REQUEST_DETAIL = "/solicitudes/service-requests/{id}/" + + # ========================================================================== + # Auth + # ========================================================================== + TOKEN = "/api/token/" + TOKEN_REFRESH = "/api/token/refresh/" diff --git a/cfg/amar/tester/tests/helpers.py b/cfg/amar/tester/tests/helpers.py new file mode 100644 index 0000000..4fa5b0b --- /dev/null +++ b/cfg/amar/tester/tests/helpers.py @@ -0,0 +1,44 @@ +""" +Contract Tests - Shared test data helpers. + +Used across all endpoint tests to generate consistent test data. +""" + +import time + + +def unique_email(prefix="test"): + """Generate unique email for test data""" + return f"{prefix}_{int(time.time() * 1000)}@contract-test.local" + + +def sample_pet_owner(email=None): + """Generate sample pet owner data""" + return { + "first_name": "Test", + "last_name": "Usuario", + "email": email or unique_email("owner"), + "phone": "1155667788", + "address": "Av. Santa Fe 1234", + "geo_latitude": -34.5955, + "geo_longitude": -58.4166, + } + + +SAMPLE_CAT = { + "name": "TestCat", + "pet_type": "CAT", + "is_neutered": False, +} + +SAMPLE_DOG = { + "name": "TestDog", + "pet_type": "DOG", + "is_neutered": False, +} + +SAMPLE_NEUTERED_CAT = { + "name": "NeuteredCat", + "pet_type": "CAT", + "is_neutered": True, +} diff --git a/station/tools/tester/tests/mascotas/__init__.py b/cfg/amar/tester/tests/mascotas/__init__.py similarity index 100% rename from station/tools/tester/tests/mascotas/__init__.py rename to cfg/amar/tester/tests/mascotas/__init__.py diff --git a/station/tools/tester/tests/mascotas/test_coverage.py b/cfg/amar/tester/tests/mascotas/test_coverage.py similarity index 100% rename from station/tools/tester/tests/mascotas/test_coverage.py rename to cfg/amar/tester/tests/mascotas/test_coverage.py diff --git a/station/tools/tester/tests/mascotas/test_pet_owners.py b/cfg/amar/tester/tests/mascotas/test_pet_owners.py similarity index 100% rename from station/tools/tester/tests/mascotas/test_pet_owners.py rename to cfg/amar/tester/tests/mascotas/test_pet_owners.py diff --git a/station/tools/tester/tests/mascotas/test_pets.py b/cfg/amar/tester/tests/mascotas/test_pets.py similarity index 100% rename from station/tools/tester/tests/mascotas/test_pets.py rename to cfg/amar/tester/tests/mascotas/test_pets.py diff --git a/station/tools/tester/tests/productos/__init__.py b/cfg/amar/tester/tests/productos/__init__.py similarity index 100% rename from station/tools/tester/tests/productos/__init__.py rename to cfg/amar/tester/tests/productos/__init__.py diff --git a/station/tools/tester/tests/productos/test_cart.py b/cfg/amar/tester/tests/productos/test_cart.py similarity index 100% rename from station/tools/tester/tests/productos/test_cart.py rename to cfg/amar/tester/tests/productos/test_cart.py diff --git a/station/tools/tester/tests/productos/test_categories.py b/cfg/amar/tester/tests/productos/test_categories.py similarity index 100% rename from station/tools/tester/tests/productos/test_categories.py rename to cfg/amar/tester/tests/productos/test_categories.py diff --git a/station/tools/tester/tests/productos/test_services.py b/cfg/amar/tester/tests/productos/test_services.py similarity index 100% rename from station/tools/tester/tests/productos/test_services.py rename to cfg/amar/tester/tests/productos/test_services.py diff --git a/station/tools/tester/tests/solicitudes/__init__.py b/cfg/amar/tester/tests/solicitudes/__init__.py similarity index 100% rename from station/tools/tester/tests/solicitudes/__init__.py rename to cfg/amar/tester/tests/solicitudes/__init__.py diff --git a/station/tools/tester/tests/solicitudes/test_service_requests.py b/cfg/amar/tester/tests/solicitudes/test_service_requests.py similarity index 100% rename from station/tools/tester/tests/solicitudes/test_service_requests.py rename to cfg/amar/tester/tests/solicitudes/test_service_requests.py diff --git a/station/tools/tester/tests/workflows/__init__.py b/cfg/amar/tester/tests/workflows/__init__.py similarity index 100% rename from station/tools/tester/tests/workflows/__init__.py rename to cfg/amar/tester/tests/workflows/__init__.py diff --git a/station/tools/tester/tests/workflows/test_turnero_general.py b/cfg/amar/tester/tests/workflows/test_turnero_general.py similarity index 100% rename from station/tools/tester/tests/workflows/test_turnero_general.py rename to cfg/amar/tester/tests/workflows/test_turnero_general.py diff --git a/data/__init__.py b/data/__init__.py new file mode 100644 index 0000000..9836af5 --- /dev/null +++ b/data/__init__.py @@ -0,0 +1,156 @@ +""" +Pawprint Data Layer + +JSON file storage (future: MongoDB) +""" + +import json +from pathlib import Path +from typing import List, Optional + +# Add parent to path for models import +import sys +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from models.pydantic import ( + Vein, Nest, Larder, Template, Tool, + Pulse, Book, Table, + VeinCollection, NestCollection, LarderCollection, + TemplateCollection, ToolCollection, + PulseCollection, BookCollection, TableCollection, + Status +) + +DATA_DIR = Path(__file__).parent.resolve() + +# Debug: print data dir on import +print(f"[data] DATA_DIR: {DATA_DIR}") +print(f"[data] DATA_DIR exists: {DATA_DIR.exists()}") +print(f"[data] veins.json exists: {(DATA_DIR / 'veins.json').exists()}") + + +def _load_json(filename: str) -> dict: + filepath = DATA_DIR / filename + if filepath.exists(): + with open(filepath) as f: + data = json.load(f) + print(f"[data] Loaded {filename}: {len(data.get('items', []))} items") + return data + print(f"[data] File not found: {filepath}") + return {"items": []} + + +def _save_json(filename: str, data: dict): + filepath = DATA_DIR / filename + with open(filepath, 'w') as f: + json.dump(data, f, indent=2) + + +# === Loaders === + +def get_veins() -> List[Vein]: + data = _load_json("veins.json") + return VeinCollection(**data).items + + +def get_nests() -> List[Nest]: + data = _load_json("nests.json") + return NestCollection(**data).items + + +def get_larders() -> List[Larder]: + data = _load_json("larders.json") + return LarderCollection(**data).items + + +def get_templates() -> List[Template]: + data = _load_json("templates.json") + return TemplateCollection(**data).items + + +def get_tools() -> List[Tool]: + data = _load_json("tools.json") + return ToolCollection(**data).items + + +def get_cabinets() -> list: + """Load cabinets (simple dict, no pydantic yet).""" + data = _load_json("cabinets.json") + return data.get("items", []) + + +def get_monitors() -> list: + """Load monitors (simple dict, no pydantic yet).""" + data = _load_json("monitors.json") + return data.get("items", []) + + +def get_pulses() -> List[Pulse]: + data = _load_json("pulses.json") + return PulseCollection(**data).items + + +def get_books() -> List[Book]: + data = _load_json("books.json") + return BookCollection(**data).items + + +def get_tables() -> List[Table]: + data = _load_json("tables.json") + return TableCollection(**data).items + + +# === Helpers === + +def get_vein(name: str) -> Optional[Vein]: + for v in get_veins(): + if v.name == name: + return v + return None + + +def get_nest(name: str) -> Optional[Nest]: + for n in get_nests(): + if n.name == name: + return n + return None + + +def get_larder(name: str) -> Optional[Larder]: + for l in get_larders(): + if l.name == name: + return l + return None + + +# === For frontend rendering === + +def get_artery_data() -> dict: + """Data for artery frontend.""" + return { + "veins": [v.model_dump() for v in get_veins()], + "nests": [n.model_dump() for n in get_nests()], + "larders": [l.model_dump() for l in get_larders()], + "pulses": [p.model_dump() for p in get_pulses()], + } + + +def get_album_data() -> dict: + """Data for album frontend.""" + return { + "templates": [t.model_dump() for t in get_templates()], + "larders": [l.model_dump() for l in get_larders()], + "books": [b.model_dump() for b in get_books()], + } + + +def get_ward_data() -> dict: + """Data for ward frontend.""" + return { + "tools": [t.model_dump() for t in get_tools()], + "monitors": get_monitors(), + "cabinets": get_cabinets(), + "nests": [n.model_dump() for n in get_nests()], + "larders": [l.model_dump() for l in get_larders()], + "tables": [t.model_dump() for t in get_tables()], + } diff --git a/data/books.json b/data/books.json index 89d1050..d201c7f 100644 --- a/data/books.json +++ b/data/books.json @@ -6,16 +6,16 @@ "title": "Architecture Model", "status": "ready", "template": null, - "depot": { + "larder": { "name": "arch-model", "slug": "arch-model", "title": "Architecture Model", "status": "ready", "source_template": null, - "data_path": "atlas/book/arch-model" + "data_path": "album/book/arch-model" }, - "output_depot": null, - "system": "atlas" + "output_larder": null, + "system": "album" }, { "name": "feature-flow", @@ -23,16 +23,16 @@ "title": "Feature Flow Pipeline", "status": "ready", "template": null, - "depot": { + "larder": { "name": "feature-flow", "slug": "feature-flow", "title": "Feature Flow Pipeline", "status": "ready", "source_template": null, - "data_path": "atlas/book/feature-flow" + "data_path": "album/book/feature-flow" }, - "output_depot": null, - "system": "atlas" + "output_larder": null, + "system": "album" }, { "name": "gherkin-samples", @@ -40,16 +40,16 @@ "title": "Gherkin Samples", "status": "ready", "template": null, - "depot": { + "larder": { "name": "gherkin-samples", "slug": "gherkin-samples", "title": "Gherkin Samples", "status": "ready", "source_template": null, - "data_path": "atlas/book/gherkin-samples" + "data_path": "album/book/gherkin-samples" }, - "output_depot": null, - "system": "atlas" + "output_larder": null, + "system": "album" }, { "name": "feature-form-samples", @@ -61,19 +61,19 @@ "slug": "feature-form", "title": "Feature Form Template", "status": "ready", - "template_path": "atlas/template/feature-form", - "system": "atlas" + "template_path": "album/template/feature-form", + "system": "album" }, - "depot": { + "larder": { "name": "feature-form", "slug": "feature-form", "title": "Feature Forms", "status": "ready", "source_template": "feature-form", - "data_path": "atlas/book/feature-form-samples/feature-form" + "data_path": "album/book/feature-form-samples/feature-form" }, - "output_depot": null, - "system": "atlas" + "output_larder": null, + "system": "album" } ] } diff --git a/data/depots.json b/data/depots.json index bb9fea0..72be870 100644 --- a/data/depots.json +++ b/data/depots.json @@ -6,7 +6,7 @@ "title": "Feature Forms", "status": "ready", "source_template": "feature-form", - "data_path": "atlas/book/feature-form-samples/feature-form" + "data_path": "album/book/feature-form-samples/feature-form" } ] } diff --git a/data/monitors.json b/data/monitors.json index 9bbc676..503de9d 100644 --- a/data/monitors.json +++ b/data/monitors.json @@ -5,18 +5,18 @@ "slug": "turnos", "title": "Turnos Monitor", "status": "dev", - "system": "station", + "system": "ward", "description": "Pipeline view of requests → turnos. Shows vet-petowner at a glance.", - "path": "station/monitor/turnos" + "path": "ward/monitor/turnos" }, { "name": "data_browse", "slug": "data-browse", "title": "Data Browse", "status": "ready", - "system": "station", - "description": "Quick navigation to test users and data states. Book/depot pattern with SQL mode for manual testing workflows.", - "path": "station/monitor/data_browse" + "system": "ward", + "description": "Quick navigation to test users and data states. Book/larder pattern with SQL mode for manual testing workflows.", + "path": "ward/monitor/data_browse" } ] } diff --git a/data/rooms.json b/data/rooms.json index 092cd52..9310a6e 100644 --- a/data/rooms.json +++ b/data/rooms.json @@ -1,11 +1,5 @@ { "items": [ - { - "name": "soleprint-local", - "slug": "soleprint-local", - "title": "Soleprint Local", - "status": "dev", - "config_path": "mainroom/soleprint" - } + {"name": "pawprint-local", "slug": "pawprint-local", "title": "Pawprint Local", "status": "dev", "config_path": "deploy/pawprint-local"} ] } diff --git a/data/cabinets.json b/data/tables.json similarity index 100% rename from data/cabinets.json rename to data/tables.json diff --git a/data/template/feature-form/template.md b/data/template/feature-form/template.md new file mode 100644 index 0000000..6b49e99 --- /dev/null +++ b/data/template/feature-form/template.md @@ -0,0 +1,38 @@ +# {{nombre_flujo}} + +## Tipo de usuario +{{tipo_usuario}} + +## Donde empieza +{{punto_entrada}} + +## Que quiere hacer el usuario +{{objetivo}} + +## Pasos + +1. {{paso_1}} +2. {{paso_2}} +3. {{paso_3}} + +## Que deberia pasar + +- {{resultado_1}} +- {{resultado_2}} + +## Problemas comunes + +- {{problema_1}} +- {{problema_2}} + +## Casos especiales + +- {{caso_especial_1}} + +## Flujos relacionados + +- {{flujo_relacionado_1}} + +## Notas tecnicas + +- {{nota_tecnica_1}} diff --git a/data/templates.json b/data/templates.json index a95b889..781b31d 100644 --- a/data/templates.json +++ b/data/templates.json @@ -6,7 +6,7 @@ "title": "Feature Form Template", "status": "ready", "template_path": "data/template/feature-form", - "system": "atlas" + "system": "album" } ] } diff --git a/data/tools.json b/data/tools.json index 0d97746..165fcc0 100644 --- a/data/tools.json +++ b/data/tools.json @@ -5,10 +5,10 @@ "slug": "tester", "title": "Contract Tests", "status": "live", - "system": "station", + "system": "ward", "type": "app", "description": "HTTP contract test runner with multi-environment support. Filter, run, and track tests against dev/stage/prod.", - "path": "station/tools/tester", + "path": "ward/tools/tester", "url": "/tools/tester/" }, { @@ -16,22 +16,33 @@ "slug": "datagen", "title": "Test Data Generator", "status": "live", - "system": "station", + "system": "ward", "type": "cli", "description": "Generate realistic test data for Amar domain (users, pets, services) and MercadoPago API responses. Used by mock veins and test seeders.", - "path": "station/tools/datagen", + "path": "ward/tools/datagen", "cli": "python -m datagen" }, + { + "name": "generate_test_data", + "slug": "generate-test-data", + "title": "DB Test Data Extractor", + "status": "dev", + "system": "ward", + "type": "cli", + "description": "Extract representative subsets from PostgreSQL dumps for testing/development.", + "path": "ward/tools/generate_test_data", + "cli": "python -m generate_test_data" + }, { "name": "modelgen", "slug": "modelgen", "title": "Model Generator", - "status": "live", - "system": "station", + "status": "dev", + "system": "ward", "type": "cli", - "description": "Generic model generation tool. Generates typed models (Pydantic, etc.) from config files, JSON Schema, or by extracting from existing codebases (Django, SQLAlchemy). Used by build.py and databrowse.", - "path": "station/tools/modelgen", - "cli": "python -m station.tools.modelgen" + "description": "Generate platform-specific models (Pydantic, Django, Prisma) from JSON Schema.", + "path": "ward/tools/modelgen", + "cli": "python -m modelgen" } ] } diff --git a/data/veins.json b/data/veins.json index 13a44dd..b5e1747 100644 --- a/data/veins.json +++ b/data/veins.json @@ -1,11 +1,11 @@ { "items": [ {"name": "jira", "slug": "jira", "title": "Jira", "status": "live", "system": "artery"}, - {"name": "amar", "slug": "amar", "title": "Amar (Mock)", "status": "ready", "system": "artery", "description": "Mock Amar API for testing turnero flow without hitting real backend"}, - {"name": "mercadopago", "slug": "mercadopago", "title": "MercadoPago (Mock)", "status": "ready", "system": "artery", "description": "Mock MercadoPago API for payment integration testing"}, + {"name": "slack", "slug": "slack", "title": "Slack", "status": "building", "system": "artery"}, + {"name": "amar", "slug": "amar", "title": "Amar API", "status": "ready", "system": "artery", "mock": true, "description": "Mock Amar backend - configure endpoint responses"}, + {"name": "mercadopago", "slug": "mercadopago", "title": "MercadoPago", "status": "ready", "system": "artery", "mock": true, "description": "Mock MercadoPago API - configure payment responses"}, {"name": "google", "slug": "google", "title": "Google", "status": "planned", "system": "artery"}, {"name": "maps", "slug": "maps", "title": "Maps", "status": "planned", "system": "artery"}, - {"name": "slack", "slug": "slack", "title": "Slack", "status": "building", "system": "artery"}, {"name": "whatsapp", "slug": "whatsapp", "title": "WhatsApp", "status": "planned", "system": "artery"}, {"name": "gnucash", "slug": "gnucash", "title": "GNUCash", "status": "planned", "system": "artery"}, {"name": "vnc", "slug": "vnc", "title": "VPN", "status": "planned", "system": "artery"}, diff --git a/mainroom/CLAUDE.md b/mainroom/CLAUDE.md index e7ae5af..ba998f0 100644 --- a/mainroom/CLAUDE.md +++ b/mainroom/CLAUDE.md @@ -2,7 +2,7 @@ ## Purpose -Mainroom orchestrates the interaction between **soleprint** and **managed rooms** (external projects like amar). +Mainroom orchestrates **soleprint + managed rooms** together (e.g., amar). Key principle: Connect soleprint to managed apps **without modifying either side**. @@ -10,130 +10,136 @@ Key principle: Connect soleprint to managed apps **without modifying either side ``` mainroom/ -├── CLAUDE.md # You are here -├── ctrl/ # Orchestration commands -│ ├── start.sh # Start services (sets env vars) -│ ├── stop.sh # Stop services -│ ├── build.sh # Build images -│ ├── logs.sh # View logs -│ ├── status.sh # Show status -│ ├── deploy.sh # Deploy to server -│ └── server/ # Server setup scripts -│ -├── sbwrapper/ # Sidebar wrapper UI -│ ├── index.html # Wrapper shell -│ ├── sidebar.css # Styling -│ ├── sidebar.js # Logic -│ └── config.json # Per-room configuration -│ -├── link/ # Adapter layer -│ ├── main.py # FastAPI service -│ ├── Dockerfile +├── amar -> ../cfg/amar # Symlink to room config +├── soleprint/ # Soleprint Docker config │ ├── docker-compose.yml -│ └── adapters/ # Framework-specific adapters -│ ├── __init__.py # BaseAdapter interface -│ └── django.py # Django adapter (for amar) -│ -└── soleprint/ # Docker configs for soleprint services - ├── docker-compose.yml - ├── docker-compose.nginx.yml - └── Dockerfile.fastapi +│ ├── docker-compose.nginx.yml +│ └── .env +├── sbwrapper/ # Sidebar wrapper UI +│ ├── config.json # Room-specific (users, Jira) +│ ├── sidebar.js +│ └── sidebar.css +└── ctrl/ # Orchestration scripts + ├── start.sh # Start all services + ├── stop.sh + ├── deploy.sh # Deploy to AWS + └── server/ # AWS setup scripts +``` + +## Usage + +### Local Development +```bash +# First, build soleprint +cd spr/ +python build.py dev --cfg amar + +# Create shared network +docker network create soleprint_network + +# Start everything +cd mainroom/ctrl +./start.sh -d # Detached +./start.sh # Foreground (logs) +./start.sh amar # Only amar +./start.sh soleprint # Only soleprint +./stop.sh # Stop all +``` + +### Deploy to AWS +```bash +cd mainroom/ctrl +./deploy.sh --dry-run # Preview +./deploy.sh # Deploy ``` ## Components -### ctrl/ - Orchestration Commands -Scripts that set env vars and start/stop services. The managed project works with its own defaults; ctrl sets overrides for orchestration. +### ctrl/ - Orchestration +| Script | Purpose | +|--------|---------| +| start.sh | Start amar + soleprint | +| stop.sh | Stop all | +| deploy.sh | rsync to AWS | +| server/ | AWS setup scripts | -```bash -./ctrl/start.sh # Start soleprint + link -./ctrl/start.sh --with-nginx # Start with nginx proxy -./ctrl/stop.sh # Stop all -./ctrl/logs.sh # View logs -./ctrl/status.sh # Show status -``` +### soleprint/ - Docker Config +Uses `SOLEPRINT_BARE_PATH` to mount gen/ into container. -### sbwrapper/ - Sidebar Wrapper UI -Collapsible sidebar overlay for ANY managed app. Provides dev tools without interfering with the managed application. +**Env vars:** +- `SOLEPRINT_BARE_PATH` - Path to gen/ +- `DEPLOYMENT_NAME` - Container prefix +- `NETWORK_NAME` - Docker network (soleprint_network) +- `SOLEPRINT_PORT` - Default 12000 + +### sbwrapper/ - Sidebar Wrapper +Collapsible sidebar overlay for managed apps. **Features:** -- Quick login panel (switch test users) -- Jira ticket info panel +- Quick login (switch test users) +- Jira ticket info - Environment info -- Collapsible, resizable -- Keyboard shortcut: `Ctrl+Shift+P` +- Keyboard: `Ctrl+Shift+P` -**Implementation:** HTML injection via nginx reverse proxy or iframe approach. - -### link/ - Adapter Layer -Framework-agnostic data navigation between soleprint and managed apps. - -**Pattern:** -``` -Managed App (DB) ←── link adapters ──→ Soleprint (Station tools) -``` - -**Endpoints:** -- `GET /health` - Health check -- `GET /api/queries` - List available queries -- `GET /api/navigate?query=` - Execute predefined query -- `GET /api/navigate?entity=&id=` - Entity navigation - -**JSON Contract:** +**config.json:** ```json { - "nodes": [{"id": "User_123", "type": "User", "label": "john", "data": {...}}], - "edges": [{"from": "User_123", "to": "Pet_456", "label": "owns"}], - "summary": {"title": "User #123", "fields": {...}} + "room_name": "amar", + "wrapper": { + "users": [ + {"id": "admin", "label": "Admin", "username": "admin@test.com", ...} + ], + "jira": {"ticket_id": "VET-535"} + } } ``` -### soleprint/ - Docker Configs -Docker compose files for running soleprint services (hub, artery, atlas, station). - -**Environment Variables:** -- `SOLEPRINT_BARE_PATH` - Path to soleprint source (gen/) -- `DEPLOYMENT_NAME` - Container prefix -- `NETWORK_NAME` - Docker network name -- `SOLEPRINT_PORT`, `ARTERY_PORT`, `ATLAS_PORT`, `STATION_PORT` +### amar/ - Room Symlink +Points to `../cfg/amar` which contains: +- docker-compose.yml +- .env +- Dockerfile.backend, Dockerfile.frontend +- databrowse/depot/, tester/tests/, monitors/, models/ ## How It Works -1. **ctrl/** sets environment variables for orchestration -2. **soleprint/** docker configs use those vars to mount code and expose ports -3. **link/** connects to managed app's database via adapters -4. **sbwrapper/** overlays UI on managed app via nginx injection - -The managed project is never modified - it runs with its own defaults, mainroom just provides the orchestration layer on top. +1. `build.py dev --cfg amar` creates gen/ with room config +2. `mainroom/amar` symlinks to `cfg/amar` +3. `ctrl/start.sh` finds docker-compose.yml in amar/ and soleprint/ +4. Both share `soleprint_network` for inter-container communication +5. sbwrapper overlays UI on managed app ## Ports | Service | Port | |---------|------| -| Soleprint Hub | 12000 | -| Artery | 12001 | -| Atlas | 12002 | -| Station | 12003 | -| Link | 8100 | +| Soleprint | 12000 | +| Amar Backend | 8000 | +| Amar Frontend | 3000 | -## Adding a New Managed Room +## Integration with ppl/ -1. Create adapter in `link/adapters/` (implement BaseAdapter) -2. Configure `sbwrapper/config.json` with room-specific users, Jira ticket, etc. -3. Set env vars in ctrl scripts pointing to managed app -4. Run `./ctrl/start.sh` +Deploy via ppl/ctrl for centralized infrastructure management: +```bash +cd /home/mariano/wdir/ppl/ctrl +./deploy-gen.sh # Build spr + deploy +./dns.sh add soleprint # Add DNS record +``` -## Worktrees +## Server Structure (mcrn.ar) -Feature development: -- `/home/mariano/wdir/wts/spr/sbwrapper` - Sidebar wrapper development -- `/home/mariano/wdir/wts/spr/databrowse` - Data browser tool +``` +~/mainroom/ +├── amar/ # Amar Docker services +├── soleprint/ # Soleprint Docker services +└── ctrl/ # Server-side scripts +``` -## External References +## External Paths -| What | Location | -|------|----------| -| Soleprint source | `../` (parent directory) | -| Amar backend | `/home/mariano/wdir/ama/amar_django_back` | -| Amar frontend | `/home/mariano/wdir/ama/amar_frontend` | -| Core room (legacy) | `/home/mariano/wdir/ama/core_room` | \ No newline at end of file +| What | Path | +|------|------| +| Amar Backend | /home/mariano/wdir/ama/amar_django_back | +| Amar Frontend | /home/mariano/wdir/ama/amar_frontend | +| Soleprint gen | /home/mariano/wdir/spr/gen | +| Pipelines | /home/mariano/wdir/ppl | diff --git a/mainroom/amar b/mainroom/amar new file mode 120000 index 0000000..577b554 --- /dev/null +++ b/mainroom/amar @@ -0,0 +1 @@ +../cfg/amar \ No newline at end of file diff --git a/mainroom/ctrl/deploy.sh b/mainroom/ctrl/deploy.sh index aa444ed..5e158d5 100755 --- a/mainroom/ctrl/deploy.sh +++ b/mainroom/ctrl/deploy.sh @@ -1,8 +1,8 @@ #!/bin/bash -# Deploy core_room to server +# Deploy mainroom to server (amar + soleprint) # # Two deployment modes: -# 1. Docker (default): Full core_room structure + source code +# 1. Docker (default): Full mainroom structure + source code # 2. Bare metal (--bare-metal): Only soleprint source to systemd services # # Usage: @@ -18,13 +18,13 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" source "$SCRIPT_DIR/.env.sync" 2>/dev/null || true SERVER="${DEPLOY_SERVER:-mariano@mcrn.ar}" -REMOTE_PATH="${DEPLOY_REMOTE_PATH:-~/core_room}" +REMOTE_PATH="${DEPLOY_REMOTE_PATH:-~/mainroom}" BARE_METAL_PATH="${DEPLOY_BARE_METAL_PATH:-~/soleprint}" # Source code paths (defaults if not in .env.sync) LOCAL_AMAR_BACKEND="${LOCAL_AMAR_BACKEND:-$HOME/wdir/ama/amar_django_back}" LOCAL_AMAR_FRONTEND="${LOCAL_AMAR_FRONTEND:-$HOME/wdir/ama/amar_frontend}" -LOCAL_SOLEPRINT="${LOCAL_SOLEPRINT:-$HOME/wdir/ama/soleprint}" +LOCAL_SOLEPRINT="${LOCAL_SOLEPRINT:-$HOME/wdir/spr/gen}" DRY_RUN="" BARE_METAL="" @@ -84,8 +84,8 @@ fi echo "=== Deploying to Docker ===" echo "" -# 1. Sync core_room structure (excluding src directories - they're synced separately) -echo "1. Syncing core_room structure..." +# 1. Sync mainroom structure (excluding src directories - they're synced separately) +echo "1. Syncing mainroom structure..." $RSYNC_CMD $DRY_RUN \ --exclude='*/src/' \ ./ \ diff --git a/mainroom/ctrl/start.sh b/mainroom/ctrl/start.sh index ed4583b..eda1d70 100755 --- a/mainroom/ctrl/start.sh +++ b/mainroom/ctrl/start.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Start core_room services +# Start mainroom services (amar + soleprint) # # Usage: # ./start.sh # Start all (foreground, see logs) @@ -14,7 +14,7 @@ set -e # Change to parent directory (services are in ../service_name) cd "$(dirname "$0")/.." -# Export core_room/.env vars so child docker-compose files can use them +# Export mainroom/.env vars so child docker-compose files can use them if [ -f ".env" ]; then set -a source .env @@ -98,5 +98,5 @@ if [ -n "$DETACH" ]; then echo "" echo "=== Services Started ===" echo "" - docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "(core_room|NAMES)" + docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "(mainroom|amar|soleprint|NAMES)" fi diff --git a/mainroom/sbwrapper/README.md b/mainroom/sbwrapper/README.md index 3634854..ac9cd59 100755 --- a/mainroom/sbwrapper/README.md +++ b/mainroom/sbwrapper/README.md @@ -1,6 +1,6 @@ -# Soleprint Wrapper - Development Tools Sidebar +# Pawprint Wrapper - Development Tools Sidebar -A collapsible sidebar that provides development and testing tools for any soleprint-managed room (like amar) without interfering with the managed application. +A collapsible sidebar that provides development and testing tools for any pawprint-managed nest (like amar) without interfering with the managed application. ## Features @@ -12,7 +12,7 @@ A collapsible sidebar that provides development and testing tools for any solepr ### 🌍 Environment Info - Display backend and frontend URLs -- Room name and deployment info +- Nest name and deployment info - Quick reference during development ### ⌨️ Keyboard Shortcuts @@ -40,7 +40,7 @@ wrapper/ Open `index.html` in your browser to see the sidebar in action: ```bash -cd core_room/wrapper +cd core_nest/wrapper python3 -m http.server 8080 # Open http://localhost:8080 ``` @@ -68,7 +68,7 @@ Edit `config.json` to customize: ```json { - "room_name": "amar", + "nest_name": "amar", "wrapper": { "enabled": true, "environment": { @@ -208,10 +208,10 @@ getSidebarHTML() { ### Add New Features -Extend the `SoleprintSidebar` class in `sidebar.js`: +Extend the `PawprintSidebar` class in `sidebar.js`: ```javascript -class SoleprintSidebar { +class PawprintSidebar { async fetchJiraInfo() { const response = await fetch('https://artery.mcrn.ar/jira/VET-123'); const data = await response.json(); @@ -294,8 +294,8 @@ Planned features (see `../WRAPPER_DESIGN.md`): ## Related Documentation - `../WRAPPER_DESIGN.md` - Complete architecture design -- `../../../soleprint/CLAUDE.md` - Soleprint framework overview -- `../../README.md` - Core room documentation +- `../../../pawprint/CLAUDE.md` - Pawprint framework overview +- `../../README.md` - Core nest documentation ## Contributing @@ -303,9 +303,9 @@ To add a new panel or feature: 1. Add HTML in `getSidebarHTML()` 2. Add styling in `sidebar.css` -3. Add logic as methods on `SoleprintSidebar` class +3. Add logic as methods on `PawprintSidebar` class 4. Update this README with usage instructions ## License -Part of the Soleprint development tools ecosystem. +Part of the Pawprint development tools ecosystem. diff --git a/mainroom/sbwrapper/index.html b/mainroom/sbwrapper/index.html index dfa6aad..55f1e54 100755 --- a/mainroom/sbwrapper/index.html +++ b/mainroom/sbwrapper/index.html @@ -3,7 +3,7 @@ - Soleprint Wrapper - Demo + Pawprint Wrapper - Demo