Metadata-Version: 2.5
Name: easycontract
Version: 0.1.0
Summary: SDK oficial de Python para el API de easycontract (firma electrónica eIDAS)
Project-URL: Documentation, https://easycontract.online/docs/api
Author-email: Román Ramírez <roman@easycontract.online>
License-Expression: LicenseRef-easycontract-proprietary
License-File: LICENSE.txt
Keywords: audit-trail,contract,e-signature,eidas,esign,webhooks
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: requests<3,>=2.31
Description-Content-Type: text/markdown

# easycontract — SDK de Python

SDK oficial del API de firma electrónica de easycontract (SES/AdES bajo eIDAS,
con paquete de evidencias defendible en litigio).

```bash
pip install easycontract
```

```python
from easycontract import EasyContract

client = EasyContract(api_key="ec_test_...")  # ec_live_... en producción
print(client.whoami())
```

- **Modo test de primera clase**: una clave `ec_test_…` opera sobre datos
  aislados (TSA/PAdES simulados). Nadie debería firmar un documento real para
  probar la integración.
- **Idempotencia automática**: todo POST lleva `Idempotency-Key` (uuid4);
  pásala tú misma para reintentos seguros de tu lado.
- **Reintentos**: 429 (respetando `Retry-After`) y 5xx en GETs.
- **Errores tipados** con el `code` estable del API:
  `AuthenticationError`, `RateLimitError`, `ForbiddenError` (cuota/feature/
  regla de membresía), `InvalidRequestError`, `NotFoundError`,
  `ConflictError`, `ServerError`.

---

## Receta 1 — Enviar un envelope desde plantilla (el flujo RootedCON)

Una sola llamada: cubre los roles, envía, y recibe los enlaces de firma.

```python
result = client.templates.create_envelope(
    template_id,
    signers={"ponente": {"full_name": "Ada Lovelace", "email": "ada@example.com"}},
    title="Contrato ponencia RootedCON 2027 — Ada Lovelace",
    send=True,  # congela hashes, plan y retención, y acuña enlaces
    deliver_emails=True,  # easycontract envía los emails con los enlaces
)
envelope_id = result["envelope"]["id"]
```

Creación de la plantilla (una vez):

```python
template = client.templates.create(name="Contrato ponente", retention_period_months=72)
doc = client.templates.add_document(template["id"], file="contrato-ponente.pdf")
client.templates.add_role(template["id"], key="ponente", label="Ponente")
client.templates.add_field(
    template["id"],
    document_id=doc["id"],
    role="ponente",
    anchor_text="Fdo. el ponente:",  # la firma se estampa donde el PDF lo dice
)
```

## Receta 2 — Firma embebida (embedded signing)

Con `deliver_emails=False` recibes el `sign_url` de un solo uso por firmante y
lo integras en tu propia aplicación (redirect o iframe):

```python
result = client.templates.create_envelope(
    template_id,
    signers={...},
    send=True,
    deliver_emails=False,
)
sign_url = result["signers"][0]["sign_url"]
# → redirige al ponente a sign_url sin salir de tu flujo
```

El enlace es personal, caduca (14 días por defecto, `expires_in_days` para
cambiarlo) y queda invalidado al firmar o rechazar.

## Receta 3 — Webhooks vs. polling

**Webhooks** (recomendado): registra un endpoint y verifica CADA entrega.

```python
endpoint = client.webhooks.create(url="https://miapp.com/hooks/easycontract")
WEBHOOK_SECRET = endpoint["secret"]  # whsec_… — se muestra UNA sola vez

# En tu receptor (Django/Flask/FastAPI):
from easycontract import webhooks

event = webhooks.verify_signature(
    payload=request.body,
    header=request.headers["X-EasyContract-Signature"],
    secret=WEBHOOK_SECRET,
)
if event["type"] == "envelope.completed":
    ...
```

**Reconciliación / polling**: los webhooks son la proyección de un event log
consultable; si pierdes una entrega, reconcilia:

```python
for event in client.events.auto_paging_iter(type="envelope.completed"):
    ...
```

## Receta 4 — Descargar el paquete de evidencias

El ZIP completo (documentos, certificado, audit trail JSON verificable
offline, sellos TSA) — lo que un tenant llevaría a un juzgado:

```python
client.envelopes.download_evidence(envelope_id, to="evidencias.zip")
client.envelopes.download_certificate(envelope_id, to="certificado.pdf")
```

## Receta 5 — Gestión de plantillas

```python
for template in client.templates.list()["data"]:
    print(template["name"])

detail = client.templates.get(template_id, expand=["documents", "roles", "fields"])
client.templates.delete(old_template_id)
```

---

## Envelope manual (sin plantilla)

```python
envelope = client.envelopes.create(
    title="NDA",
    retention_period_months=60,  # retención SIEMPRE explícita
)
client.envelopes.add_document(envelope["id"], file="nda.pdf")
client.envelopes.add_signer(
    envelope["id"],
    full_name="Grace Hopper",
    email="grace@example.com",
    auth_methods=["email_link", "sms_otp"],
    phone="+34600111222",  # OTP: plan Pro+
)
sent = client.envelopes.send(envelope["id"])
```

## Organización, miembros e invitaciones

```python
org = client.organization.get()  # plan, límites y features del plan
members = client.members.list()
invite = client.invitations.create(email="colega@example.com", role="member")
# invite["accept_url"] — entrégalo tú o deja que easycontract lo envíe por email
```
