Un e-commerce colombiano de referencia técnica. Es un espejo sanitizado de un desarrollo real que llegó a producción — mismo stack, mismas integraciones, misma arquitectura, sin datos de cliente, sin credenciales y sin historia heredada.
No es un tutorial ni un boilerplate. Es el código que sobrevivió al contacto con la DIAN, con una pasarela de pagos y con un cliente real, con lo identificable retirado y el catálogo reemplazado por datos ficticios.
Demo: alethia.veridisdev.com
Un carrito con Stripe lo hace cualquiera. El valor de este repo no es el e-commerce: es el ecosistema colombiano, que es donde un proyecto real se atasca y donde no hay tutoriales que copiar.
- Facturar ante la DIAN a través de Alegra, con el modelo fiscal colombiano encima.
- Despachar con Coordinadora vía Envia.com: guías, novedades, festivos colombianos en el cálculo de días hábiles.
- Cobrar con Wompi, que no es Stripe: Web Checkout por redirección, firma de integridad, webhook como única fuente de verdad del pago.
Eso es lo que el repo demuestra. El resto —catálogo, carrito, reseñas, cupones— es el andamio necesario para que esas tres cosas tengan dónde ocurrir.
Esto se declara explícitamente porque la honestidad es parte del valor. Un repo de portafolio que no distingue lo que funciona de lo que está simulado no sirve para evaluar a nadie.
| Integración | Estado |
|---|---|
| Wompi (pagos) | 🟢 Sandbox real, end-to-end. Web Checkout por redirección, webhook funcional, verificación de firma de integridad, re-consulta del API antes de acreditar el pago. Ninguna transacción es real. |
| Alegra — facturación electrónica / DIAN | 🟡 Mockeada con respuestas realistas. Detrás del feature flag ALEGRA_FACTURACION_ACTIVA, que por defecto construye y registra el payload sin llamar a ningún servicio externo (apps/facturacion/tasks.py:125). |
| Alegra — importador de catálogo | 🟠 Cliente HTTP real, sin feature flag. apps/productos/services/alegra.py habla con https://api.alegra.com/api/v1/ (línea 19) por requests.get (95) y requests.post (151). Con credenciales en el entorno, sale tráfico de verdad. Lo usan el comando importar_alegra y productos/views_admin.py. |
| Coordinadora / Envia.com (envíos) | 🟡 Mockeada con respuestas realistas. |
Alegra ocupa dos filas porque decir "Alegra está mockeada" era falso a medias: el flag protege la facturación, no el importador de catálogo. Declararlo cuesta una fila; descubrirlo en runtime cuesta más.
El catálogo y los usuarios son ficticios. Los genera seed_demo; los productos llevan sufijo -demo y el comando se niega a ejecutarse si DEBUG=False.
- Web Checkout (redirección), no widget JS. El widget embebido devuelve 403 de CloudFront en local.
- Wompi rechaza un
redirect-urlque apunte alocalhost— no es dominio público, y responde 403 genérico. - Una referencia ya usada devuelve 403: no se pueden reusar.
- La verdad del pago es el webhook, no el redirect ni el callback. El frontend hace polling del estado.
- El webhook tiene tres defensas: valida la firma del evento, re-consulta el API de Wompi antes de acreditar, y comprueba que el monto del API coincida con el de la transacción. Idempotente vía
select_for_update.
| Capa | Tecnología |
|---|---|
| Backend | Django 5.0 + Django REST Framework 3.15 |
| Async | Celery + Redis |
| DB | PostgreSQL 16 |
| Frontend | Next.js 14 App Router + TypeScript |
| Imágenes | Cloudinary |
| Auth | JWT. Google OAuth está implementado pero desactivado: OAUTH_GOOGLE_HABILITADO = False está fijado en duro en backend/config/settings/base.py:215 — no se lee del entorno, así que no hay forma de encenderlo por configuración. El código (apps/usuarios/views_oauth.py) y sus rutas existen y están registradas; la vista comprueba el flag y corta. |
| Correo | Resend |
| Infra | Docker + Docker Compose + Nginx |
| Deploy | Backend en Railway · Frontend en Vercel |
Necesitas Docker y Node 20+.
# 1. Configuración. UN SOLO .env, en la raíz: sirve a backend e infraestructura.
# No crees backend/.env — python-decouple busca subiendo desde el CWD y
# ensombrecería este de forma invisible.
cp .env.example .env
# Mínimo para arrancar: SECRET_KEY, DB_*, DJANGO_SUPERUSER_EMAIL y
# DJANGO_SUPERUSER_PASSWORD. El resto es opcional y está marcado en el archivo.
cp frontend/.env.example frontend/.env.local
# 2. Stack completo (Postgres, Redis, backend, worker, frontend)
docker compose up -d
# 3. Base de datos
docker compose exec backend python manage.py migrate
# 4. Catálogo ficticio (requiere DEBUG=True)
docker compose exec backend python manage.py seed_demo
# Para limpiarlo:
# docker compose exec backend python manage.py purgar_demo
# 5. Superusuario — lee las credenciales del entorno, nunca del código
docker compose exec backend python scripts/create_superuser.pyEl frontend queda en http://localhost:3000 y el panel de administración en /alethia-control/.
Para trabajar en el frontend fuera de Docker:
cd frontend
npm ci
npm run devEste repo se construye con subagentes especializados. .claude/agents/ contiene 16 agentes curados para este stack: 8 de engineering, 4 de testing, 3 de design y 1 de producto.
Vienen de una colección open source de terceros bajo licencia MIT y se incluyen sin modificar; la curaduría —elegir 16 y adaptarlos a este stack— es lo propio. Los detalles y la licencia original están en .claude/agents/ATTRIBUTION.md.
Tres reglas que salieron de usarlos en serio:
- La escritura es serial. Dos subagentes sobre los mismos archivos se pisan. La auditoría (read-only) sí paraleliza bien.
- Los subagentes no heredan el
CLAUDE.md. Las constraints hay que repetirlas literal en cada prompt. - Un solo archivo de escritura por agente, declarado en el prompt.
El registro completo —el caso del rebrand visual, los hallazgos con los que los agentes refutaron su propio brief, y los pasos que deliberadamente no se delegaron— está en docs/proceso-agentico.md.
Sin adornos:
Hay un solo módulo con tests: apps/pagos. Los otros diez tests.py siguen vacíos, a 0 bytes. La cobertura del repo es deliberadamente estrecha: se empezó por la pieza donde un fallo significa acreditar un pago que no ocurrió, no por subir un porcentaje.
Son 18 casos sobre la criptografía de Wompi:
- Firma de integridad del Web Checkout: vector conocido con hash congelado, determinismo, y el efecto de
expirationsobre la cadena firmada. - Verificación de firma de evento: la firma válida pasa; se rechazan checksum incorrecto, monto manipulado, estado manipulado, timestamp manipulado, seis formas de payload malformado, y el caso de
EVENTS_SECRETsin configurar. - Webhook:
POST /api/pagos/webhook/wompi/con firma inválida responde 401.
cd backend
pip install -r requirements/dev.txt
python -m pytest apps/pagos/ -v # 18 passed en ~0.3sLa suite no necesita PostgreSQL ni Redis, y eso es una afirmación verificada, no una suposición. Ningún test lleva el marker django_db, así que pytest-django bloquea el ORM; que el test del webhook pase con el bloqueo puesto es la prueba de que la verificación de firma rechaza antes de la primera query. Si alguien introdujera una consulta en ese camino, el test fallaría. La caché se apunta a memoria en config/settings/test.py porque el throttle del webhook consulta Redis antes del handler.
Pendiente, en orden:
Verificación de firma del webhook de Wompi (unitario, sin DB).✅- Tests de integración contra PostgreSQL. Aquí sí hará falta
services: postgresen CI y el markerdjango_db:apps/productos/migrations/0002_unaccent_extension.pyusaUnaccentExtension, exclusivo de PostgreSQL, así que SQLite no es una salida. - E2E con Playwright sobre el flujo de checkout.
- Coverage gates en CI. Hoy
pytest-covestá instalado pero no hay umbral configurado.
CI: .github/workflows/ci.yml corre en cada push y pull request — pytest sobre apps/pagos/ y typecheck de TypeScript en el frontend. Sin servicios: ambos jobs son solo instalar y correr.
Tres contratos que hoy dependen de que literales separados coincidan por convención, sin que nada lo garantice. No son bugs; son sitios donde un renombre descuidado rompe algo en runtime sin ningún error de compilación:
ADMIN_URLse define enconfig/settings/base.pyy no lo consume nadie:config/urls.pydeclara la ruta del panel en paralelo, con su propio literal. Si divergen, el admin responde 404 solo en producción, donde nginx enruta ese prefijo por su cuenta — un tercer literal ennginx/nginx.conf.- Las
SHOP_*(SHOP_CITY_CODE,SHOP_ADDRESS,SHOP_PHONE,SHOP_EMAIL) se leen del entorno en settings y ningún módulo las usa. Igual queVERSION_TERMINOSyVERSION_PRIVACIDAD: la versión de consentimiento que se persiste sale en realidad deConfiguracionGlobal, no de esas variables. - La cookie del carrito anónimo tiene tres fuentes de verdad independientes: una constante en
apps/carrito/views.py, otra enutils/cookies.py, y un literal suelto enapps/usuarios/views.py. Las tres deben decir lo mismo o el carrito anónimo se rompe al iniciar sesión.
Y el backlog abierto tras la revisión pre-publicación (registro completo). Se lista en vez de esconderse: un repo de referencia que declara su deuda es más útil que uno que la maquilla.
frontend/src/lib/api.ts:4usa el fallback'http://localhost:8000/api', pero el valor documentado (frontend/.env.example:20) va sin/apiy los call sites ya prefijan/api/. Si la variable falta, cada llamada iría a/api/api/…. Su hermanofetchServer.ts:3lo hace bien.docker-compose.prod.yml:85-92nunca hornea lasNEXT_PUBLIC_*en el build: Next las inlinea en build time y elenv_filees runtime.apps/facturacion/tasks.py:131loguea nombre y cédula a INFO, contra la minimización de datos que el propio repo aplica enapps/pedidos/views.py.- Los docstrings de
apps/pagos/services/wompi.pyhablan de "widget" cuando la arquitectura es Web Checkout por redirección — el widget es justo lo que no funcionó. purgar_demono tiene guard deDEBUGmientrasseed_demosí (seed_demo.py:1288). El destructivo es el que no lo lleva.docker-compose.yml:97-98monta el.enventero en el contenedor del frontend.utils/throttling.pymarca un "PENDIENTE" ya hecho yLoginRateThrottleno lo usa ninguna vista..env.example:69entregaJWT_REFRESH_TOKEN_LIFETIME_DAYS=7mientras el default del código (base.py:170) es2, y el comentario dos líneas antes dice "2 días" — el fichero se contradice a sí mismo.- La home promete "5 días hábiles · Ley 1480" (
ConfianzaSection.tsx:27) sin documento en el sitio que lo respalde. seed_demo.py:166conserva7C4DB5, lila de la paleta vieja, como miniatura de un producto demo.
Los dos primeros son lectura, no runtime: confirmarlos exige construir el bundle. Están declarados en vez de arreglados precisamente por eso.
El catálogo, las marcas, las reseñas y los usuarios son ficticios y los genera seed_demo. Las marcas de proveedor del importador son inventadas. Nada de lo que ves en la demo corresponde a un negocio real.
Desarrollado por Veridis Dev.
Los agentes de .claude/agents/ son de terceros bajo licencia MIT — ver ATTRIBUTION.md. El código de este repositorio todavía no declara licencia.