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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install e2e dependencies
run: |
cd frontend
npm ci
npx playwright install --with-deps chromium
- name: Prepare env files
run: |
cp .env.example .env
Expand Down Expand Up @@ -124,6 +134,20 @@ jobs:
docker compose logs backend
docker compose logs db
exit 1
- name: Run Playwright e2e
env:
E2E_BASE_URL: http://127.0.0.1:53000
E2E_API_URL: http://127.0.0.1:58000/api
run: |
cd frontend
npm run e2e
- name: Upload Playwright report
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: frontend/playwright-report/
if-no-files-found: ignore
- name: Shutdown stack
if: always()
run: docker compose down -v
197 changes: 100 additions & 97 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,132 +1,135 @@
# AutoParts Integracion
# AutoParts ? E-commerce B2C/B2B con Backoffice y Webpay

Proyecto full-stack de e-commerce automotriz orientado a portafolio profesional.
AutoParts es un proyecto full-stack de e-commerce automotriz pensado para portfolio profesional: cat?logo B2C, canal mayorista B2B, carrito, ?rdenes, Webpay sandbox, backoffice administrativo, documentaci?n t?cnica y CI. La intenci?n no es mostrar una demo aislada, sino un flujo de producto defendible de punta a punta.

## Qu? demuestra

- Backend Django + DRF con JWT, roles y contratos documentados.
- Flujo de compra completo: cat?logo ? carrito ? orden ? Webpay sandbox ? confirmaci?n.
- Canal B2B protegido con precios efectivos por rol distribuidor/admin.
- Backoffice para productos, categor?as, pagos, ?rdenes, usuarios y m?tricas.
- Docker Compose para levantar PostgreSQL, API y frontend en local.
- Observabilidad b?sica: healthcheck, logs JSON y `X-Request-ID`.

## Stack
- Backend: Django + DRF + JWT + Webpay (sandbox)
- Frontend: React + React Router + Axios
- DB: PostgreSQL (entorno estandar)
- Infra local: Docker Compose
- CI: GitHub Actions

## Arquitectura
- `backend/`: API REST, autenticacion, carrito, ordenes, pagos y backoffice.
- `frontend/`: app publica (B2C), canal B2B y backoffice admin React.
- `docs/`: guias de operacion, API y framework de trabajo con agentes.

## Inicio rapido (Docker)
1. Copiar variables:
- `copy .env.example .env`
- `copy backend\.env.example backend\.env`
- `copy frontend\.env.example frontend\.env`
2. Configurar secretos del proyecto (especialmente `DJANGO_SECRET_KEY`).
- Webpay queda listo por defecto con credenciales publicas de integracion (sandbox).
- Opcional: ajustar puertos host via `.env`:
- `POSTGRES_HOST_PORT` (default `5432`)
- `BACKEND_HOST_PORT` (default `8000`)
- `FRONTEND_HOST_PORT` (default `3000`)
- El stack Docker ejecuta bootstrap demo al iniciar (`BOOTSTRAP_PORTFOLIO=true` por defecto).
- Para desactivarlo, define `BOOTSTRAP_PORTFOLIO=false` en el `.env` raiz.

| Capa | Tecnolog?a |
| --- | --- |
| Backend | Django, Django REST Framework, Simple JWT, drf-spectacular |
| Frontend | React, React Router, Axios, React Testing Library, Playwright |
| Base de datos | PostgreSQL |
| Infra local | Docker Compose |
| Pagos | Webpay sandbox / integraci?n Transbank |
| Calidad | GitHub Actions, tests backend/frontend, lint, Docker smoke, e2e |

## Arquitectura r?pida

```text
backend/ API REST, auth, dominio ecommerce, Webpay, backoffice y tests
docs/ referencia API, auditor?a, playbook operativo y planes
frontend/ tienda B2C, canal B2B, backoffice React y e2e
.github/ templates y workflow CI
```

## Capturas / demo visual

> Las capturas son placeholders de portfolio generados para documentar el recorrido visual. Reemplazar por screenshots reales cuando se publique una demo p?blica.

| Home | Cat?logo | Backoffice |
| --- | --- | --- |
| ![Home placeholder](docs/assets/screenshots/home-preview.svg) | ![Cat?logo placeholder](docs/assets/screenshots/catalog-preview.svg) | ![Backoffice placeholder](docs/assets/screenshots/admin-preview.svg) |

## Inicio r?pido con Docker

1. Copiar variables de ejemplo:

```bash
copy .env.example .env
copy backend\.env.example backend\.env
copy frontend\.env.example frontend\.env
```

2. Revisar secretos locales:

- `DJANGO_SECRET_KEY`: usar un valor propio en local.
- Configurar `WEBPAY_COMMERCE_CODE` y `WEBPAY_API_KEY` con credenciales sandbox/integraci?n propias.
- No usar credenciales demo ni valores de ejemplo en producci?n.

3. Levantar stack:

```bash
docker compose up --build
```
4. URLs:

4. URLs principales:

- Frontend: `http://localhost:3000`
- Backend API: `http://localhost:8000/api`
- Healthcheck: `http://localhost:8000/api/health/`
- OpenAPI schema: `http://localhost:8000/api/schema/`
- Swagger UI: `http://localhost:8000/api/docs/swagger/`
- ReDoc: `http://localhost:8000/api/docs/redoc/`

## Bootstrap demo (portfolio)
Con stack levantado:
```bash
docker compose exec backend python manage.py bootstrap_portfolio
```
## Usuarios demo locales

El stack Docker ejecuta bootstrap demo por defecto (`BOOTSTRAP_PORTFOLIO=true`). Las credenciales son **solo para entorno local/demo**.

| Rol | Usuario | Password | Uso |
| --- | --- | --- | --- |
| Admin | `admin_portfolio` | `PORTFOLIO_ADMIN_PASSWORD` | Backoffice `/admin-app` |
| Cliente | `cliente_demo` | `PORTFOLIO_CUSTOMER_PASSWORD` | Compra B2C |
| Distribuidor | `dist_demo` | `PORTFOLIO_DISTRIBUTOR_PASSWORD` | Cat?logo B2B |

Para rehidratar datos demo manualmente:

Para resetear stock demo completo del catalogo (util despues de varias compras QA):
```bash
docker compose exec backend python manage.py bootstrap_portfolio --reset-stock
docker compose exec backend python manage.py bootstrap_portfolio --reset-stock --force-passwords
```

Este comando:
- Carga `api/fixtures/products.json` si no hay productos.
- Crea/actualiza usuarios demo `admin_portfolio`, `cliente_demo`, `dist_demo`.
- Reequilibra stock agotado (o todo el catalogo con `--reset-stock`).
- Permite override por variables `PORTFOLIO_*` en `backend/.env`.

## Endpoints clave
- Auth: `POST /api/token/`, `POST /api/token/refresh/`, `POST /api/register/`
- Catalogo: `GET /api/products/` (`q`, `category`, `channel=b2b`)
- Carrito: `GET/POST /api/cart/`, `PATCH/DELETE /api/cart/{id}/`
- Ordenes: `POST /api/orders/`, `GET /api/orders/{id}/`, `GET /api/orders/user/`
- Webpay: `POST /api/webpay/init/`, `POST /api/webpay/return/`, `POST /api/webpay/commit/`
- Backoffice admin:
- `GET /api/admin/metrics/`
- `GET/POST/PATCH/DELETE /api/admin/products/`
- `GET/POST/DELETE /api/admin/categories/`
- `GET/PATCH /api/admin/orders/`
- `GET/PATCH /api/admin/payments/`
- `GET/PATCH /api/admin/users/`

## Roles
- `customer`: compra retail.
- `distributor`: acceso a canal B2B.
- `admin`: backoffice y administracion.

Nota: el registro publico solo permite `customer` y `distributor`.

Flujo de compra:
- `POST /api/orders/` prepara la orden para pago.
- El carrito se mantiene hasta pago exitoso.
- Stock y conciliacion de carrito se aplican en `POST /api/webpay/commit/` autorizado.

## Calidad y pruebas
## Flujos clave

- Registro p?blico: solo `customer` y `distributor`.
- Login JWT con claims de `role` y `username`.
- Cat?logo p?blico: `GET /api/products/` con filtros `q`, `search`, `category`.
- Cat?logo B2B: `channel=b2b`, requiere rol `distributor` o `admin`.
- Orden: `POST /api/orders/` prepara el pago sin descontar stock todav?a.
- Webpay: `POST /api/webpay/init/` y `POST /api/webpay/commit/` cierran el flujo.
- Backoffice: m?tricas, productos, categor?as, pagos, ?rdenes y usuarios.

## Calidad y evidencia

Comandos esperados por CI y QA local:

Backend:

```bash
cd backend
python manage.py check
python manage.py test
```

Frontend:

```bash
cd frontend
npm ci
npm run build
npm run lint
npm run test:ci
npm run e2e
npm audit --omit=dev --audit-level=moderate
```

## Observabilidad
- Logs backend en formato JSON estructurado.
- Header de correlacion `X-Request-ID` en todas las respuestas API.
- Eventos criticos trazados: creacion de orden, init/commit Webpay y cambios admin de pagos.
> Por regla del repo, no ejecutar build local despu?s de cambios; el build se valida en GitHub Actions.

## Flujo de trabajo recomendado
0. Inicializar git (si el directorio no esta versionado):
```bash
git init
```
1. Crear rama con alcance acotado.
2. Implementar cambios pequenos y verificables.
3. Abrir PR con evidencia de pruebas y riesgos.
4. Usar checklist de `.github/pull_request_template.md`.

## Roadmap resumido
- Seguridad y configuracion por entorno: completado.
- Robustez backend + Webpay + RBAC: completado base.
- B2B y backoffice React: completado base funcional.
- Refinamiento UI/UX premium (Fase 4): completado base + iteracion continua.
- Mejor cobertura frontend E2E y observabilidad avanzada: siguiente iteracion.

## Documentacion relacionada
- [API](docs/API.md)
- [Contribucion](CONTRIBUTING.md)
- [AGENTS](AGENTS.md)
## Seguridad y tradeoffs

- El proyecto usa JWT en `localStorage` para simplificar la demo local. En producci?n conviene usar cookies `HttpOnly`, rotaci?n de refresh tokens y protecci?n CSRF acorde al despliegue.
- Webpay usa sandbox. `.env.example` usa placeholders; configura tus credenciales de integraci?n localmente.
- Los archivos `.env` reales est?n ignorados por Git.

## Documentaci?n relacionada

- [Referencia API](docs/API.md)
- [Auditor?a de cierre](docs/FINAL_AUDIT.md)
- [Contribuci?n](CONTRIBUTING.md)
- [Playbook de agentes](docs/AGENT_PLAYBOOK.md)
- [Mapa MCP](docs/MCP_MAP.md)
- [Tracker del plan](docs/PLAN_TRACKER.md)
- [Plan visual Fase 4](docs/UIUX_PHASE4_PLAN.md)
- [Auditoria final](docs/FINAL_AUDIT.md)
14 changes: 7 additions & 7 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,24 @@ JWT_REFRESH_DAYS=1
API_PAGE_SIZE=12
B2B_DISCOUNT_PERCENT=12
WEBPAY_HOST=https://webpay3gint.transbank.cl
WEBPAY_COMMERCE_CODE=597055555532
WEBPAY_API_KEY=579B532A7440BB0C9079DED94D31EA1615BACEB56610332264630D42D0A36B1C
WEBPAY_COMMERCE_CODE=replace-me-with-webpay-sandbox-commerce-code
WEBPAY_API_KEY=replace-me-with-webpay-sandbox-api-key
WEBPAY_TIMEOUT_SECONDS=20
FRONTEND_URL=http://localhost:3000
DJANGO_LOG_LEVEL=INFO
BOOTSTRAP_PORTFOLIO=false
PORTFOLIO_ADMIN_USERNAME=admin_portfolio
PORTFOLIO_ADMIN_EMAIL=admin@autoparts.local
PORTFOLIO_ADMIN_PASSWORD=Admin123!
PORTFOLIO_ADMIN_PASSWORD=local-demo-admin-password
PORTFOLIO_CUSTOMER_USERNAME=cliente_demo
PORTFOLIO_CUSTOMER_EMAIL=cliente@autoparts.local
PORTFOLIO_CUSTOMER_PASSWORD=Cliente123!
PORTFOLIO_CUSTOMER_PASSWORD=local-demo-customer-password
PORTFOLIO_DISTRIBUTOR_USERNAME=dist_demo
PORTFOLIO_DISTRIBUTOR_EMAIL=dist@autoparts.local
PORTFOLIO_DISTRIBUTOR_PASSWORD=DistDemo123!
PORTFOLIO_DISTRIBUTOR_PASSWORD=local-demo-distributor-password
PORTFOLIO_CUSTOMER_QA_USERNAME=cliente_qa
PORTFOLIO_CUSTOMER_QA_EMAIL=cliente.qa@autoparts.local
PORTFOLIO_CUSTOMER_QA_PASSWORD=ClienteQA123!
PORTFOLIO_CUSTOMER_QA_PASSWORD=local-demo-customer-qa-password
PORTFOLIO_DISTRIBUTOR_QA_USERNAME=dist_qa
PORTFOLIO_DISTRIBUTOR_QA_EMAIL=dist.qa@autoparts.local
PORTFOLIO_DISTRIBUTOR_QA_PASSWORD=DistQA123!
PORTFOLIO_DISTRIBUTOR_QA_PASSWORD=local-demo-distributor-qa-password
10 changes: 5 additions & 5 deletions backend/api/management/commands/bootstrap_portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,39 +52,39 @@ def _ensure_demo_users(self, force_passwords: bool):
{
"username": os.getenv("PORTFOLIO_ADMIN_USERNAME", "admin_portfolio"),
"email": os.getenv("PORTFOLIO_ADMIN_EMAIL", "admin@autoparts.local"),
"password": os.getenv("PORTFOLIO_ADMIN_PASSWORD", "Admin123!"),
"password": os.getenv("PORTFOLIO_ADMIN_PASSWORD", "local-demo-admin-password"),
"role": "admin",
"is_staff": True,
"is_superuser": True,
},
{
"username": os.getenv("PORTFOLIO_CUSTOMER_USERNAME", "cliente_demo"),
"email": os.getenv("PORTFOLIO_CUSTOMER_EMAIL", "cliente@autoparts.local"),
"password": os.getenv("PORTFOLIO_CUSTOMER_PASSWORD", "Cliente123!"),
"password": os.getenv("PORTFOLIO_CUSTOMER_PASSWORD", "local-demo-customer-password"),
"role": "customer",
"is_staff": False,
"is_superuser": False,
},
{
"username": os.getenv("PORTFOLIO_DISTRIBUTOR_USERNAME", "dist_demo"),
"email": os.getenv("PORTFOLIO_DISTRIBUTOR_EMAIL", "dist@autoparts.local"),
"password": os.getenv("PORTFOLIO_DISTRIBUTOR_PASSWORD", "DistDemo123!"),
"password": os.getenv("PORTFOLIO_DISTRIBUTOR_PASSWORD", "local-demo-distributor-password"),
"role": "distributor",
"is_staff": False,
"is_superuser": False,
},
{
"username": os.getenv("PORTFOLIO_CUSTOMER_QA_USERNAME", "cliente_qa"),
"email": os.getenv("PORTFOLIO_CUSTOMER_QA_EMAIL", "cliente.qa@autoparts.local"),
"password": os.getenv("PORTFOLIO_CUSTOMER_QA_PASSWORD", "ClienteQA123!"),
"password": os.getenv("PORTFOLIO_CUSTOMER_QA_PASSWORD", "local-demo-customer-qa-password"),
"role": "customer",
"is_staff": False,
"is_superuser": False,
},
{
"username": os.getenv("PORTFOLIO_DISTRIBUTOR_QA_USERNAME", "dist_qa"),
"email": os.getenv("PORTFOLIO_DISTRIBUTOR_QA_EMAIL", "dist.qa@autoparts.local"),
"password": os.getenv("PORTFOLIO_DISTRIBUTOR_QA_PASSWORD", "DistQA123!"),
"password": os.getenv("PORTFOLIO_DISTRIBUTOR_QA_PASSWORD", "local-demo-distributor-qa-password"),
"role": "distributor",
"is_staff": False,
"is_superuser": False,
Expand Down
45 changes: 45 additions & 0 deletions backend/api/migrations/0007_product_non_negative_constraints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import django.core.validators
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("api", "0006_alter_cartitem_options_alter_category_options_and_more"),
]

operations = [
migrations.AlterField(
model_name="product",
name="price",
field=models.DecimalField(
decimal_places=2,
max_digits=10,
validators=[django.core.validators.MinValueValidator(0)],
verbose_name="Precio CLP",
),
),
migrations.AlterField(
model_name="product",
name="quantity",
field=models.IntegerField(
default=0,
validators=[django.core.validators.MinValueValidator(0)],
verbose_name="Cantidad en stock",
),
),
migrations.AddConstraint(
model_name="product",
constraint=models.CheckConstraint(
check=models.Q(("price__gte", 0)),
name="product_price_non_negative",
),
),
migrations.AddConstraint(
model_name="product",
constraint=models.CheckConstraint(
check=models.Q(("quantity__gte", 0)),
name="product_quantity_non_negative",
),
),
]
Loading
Loading