# Investigación: enviar cotizaciones del CRM a Alegra (Etapa 2)

> **Estado:** investigación. **No** se implementa el envío todavía. Objetivo:
> definir qué datos deben existir **sí o sí** en el CRM para que, en la Etapa 2,
> una cotización generada en el CRM pueda mandarse automáticamente a Alegra y,
> de ahí, poder convertirse en factura (CFDI 4.0) en México.
>
> API base: `https://api.alegra.com/api/v1/` · Auth: Basic (email + API token).

---

## 1. Cómo arma Alegra una cotización (`estimate`)

Una cotización en Alegra **no es un documento fiscal**, pero:

1. Debe estar **asociada a un contacto** (cliente) que **ya exista en Alegra**
   (se referencia por `client.id`).
2. Debe tener **al menos un producto/servicio**; cada línea referencia un ítem
   que **ya existe en Alegra** (`items[].id`) o se manda con `name` + `price`.

➡️ Esto implica un **flujo de 3 pasos** en el adapter (Etapa 2):

```
1) Sincronizar/crear el CONTACTO en Alegra   → POST /contacts   → guardamos su id
2) Sincronizar/crear cada PRODUCTO en Alegra → POST /items      → guardamos su id
3) Crear la COTIZACIÓN en Alegra             → POST /estimates  → guardamos su id
```

Por eso el CRM necesita **guardar los `id` de Alegra** (mapeo) de contacto,
empresa, producto y cotización; si no, cada envío duplicaría registros.

---

## 2. Campos por recurso (según la API de Alegra)

### `POST /estimates` (cotización)

| Campo | ¿Requerido? | Origen en el CRM |
|---|---|---|
| `date` (yyyy-MM-dd) | **Sí** | `quote.createdAt` ✅ |
| `dueDate` (vigencia) | **Sí** | ⚠️ **falta**: la cotización no guarda vencimiento |
| `client.id` | **Sí** | ⚠️ **falta**: id de Alegra del contacto/empresa |
| `items[].id` | **Sí** | ⚠️ **falta**: id de Alegra de cada producto |
| `items[].price` | **Sí** | `quoteItem.priceWithoutVatSnapshot` ✅ |
| `items[].quantity` | **Sí** | `quoteItem.quantity` ✅ |
| `items[].tax` | Opc. | ⚠️ Alegra usa **id de impuesto**, no `0.16`. Falta mapear "IVA 16%" → tax id de Alegra |
| `items[].discount` | Opc. | (no usamos descuentos aún) |
| `observations` / `anotation` | Opc. | `quote.notesInternal` / `quote.notesPublic` ✅ |
| `seller`, `priceList`, `warehouse`, `numberTemplate` | Opc. | configuración |

### `POST /contacts` (cliente / receptor)

| Campo | ¿Requerido por Alegra? | ¿Requerido para factura MX (CFDI 4.0)? | Origen en el CRM |
|---|---|---|---|
| `name` | **Sí** | Sí (razón social exacta de la Constancia) | `contact.fullName` / `company.fiscalName` ✅ |
| `identification` (RFC) | No | **Sí** | `company.rfc` ✅ · ⚠️ **falta en contactos** (persona física) |
| `regimeObject` (régimen fiscal SAT) | No | **Sí** | ⚠️ **falta** |
| Uso del CFDI | No | **Sí** | ⚠️ **falta** |
| `address.zipCode` (CP fiscal) | No | **Sí** (SAT valida RFC + régimen + CP) | ⚠️ **falta** (el `postalCode` de la cotización es de entrega, no fiscal) |
| Tipo de persona (física/moral) | No | **Sí** | ⚠️ **falta** (hoy es implícito: empresa=moral, contacto=física) |
| `thirdType` (NATIONAL/FOREIGN) | No | Sí (default NATIONAL) | ⚠️ **falta** (default nacional) |
| `email` | No | No | `contact.email` ✅ |
| `phonePrimary` / `mobile` | No | No | `contact.whatsapp` ✅ |

### `POST /items` (producto/servicio)

| Campo | ¿Requerido por Alegra? | ¿Requerido para factura MX? | Origen en el CRM |
|---|---|---|---|
| `name` (≤150) | **Sí** | Sí | `product.name` ✅ |
| `price` | **Sí** | Sí | `product.price_without_vat_mxn` ✅ |
| `reference` (SKU ≤45) | No | Recom. | `product.sku` ✅ |
| `description` (≤500) | No | No | `product.description` ✅ |
| `tax` (id de impuesto) | No | **Sí** (IVA) | ⚠️ mapear `vat_rate` → tax id Alegra |
| `productKey` (clave SAT producto/servicio) | No | **Sí** | ⚠️ **falta** |
| `claveUnidad` (clave SAT de unidad) | No | **Sí** | ⚠️ **falta** |

> Alegra expone los catálogos SAT por API (régimen, uso CFDI, claves de producto
> `config/getProductKeys`, unidades), así que esos códigos se **eligen de una
> lista**, no se escriben a mano.

---

## 3. Qué debe existir SÍ O SÍ en el CRM

### A) Mínimo para que la cotización **llegue** a Alegra
Aunque no se facture, sin esto el envío falla o duplica:

1. **Mapeo de ids de Alegra** (se llenan solos en la 1ª sincronización, pero el
   modelo debe tener los campos):
   - `contact.alegraId`, `company.alegraId`
   - `product.alegraId`
   - `quote.alegraEstimateId`
2. **Vigencia de la cotización**: `quote.expiresAt` (o `validUntil`).
3. **Mapeo de impuesto**: el id del impuesto "IVA 16%" en Alegra (config global).

### B) Mínimo para que esa cotización pueda volverse **factura (CFDI 4.0)**
Estos son los datos fiscales que hay que **capturar del cliente** (idealmente
desde su Constancia de Situación Fiscal):

4. **RFC** del receptor — en `company.rfc` (✅) y **agregar `rfc` a contactos**.
5. **Razón social** exacta — `company.fiscalName` (✅) / agregar a contactos.
6. **Régimen fiscal SAT** (código de catálogo) — **nuevo campo**.
7. **Uso del CFDI** (código de catálogo) — **nuevo campo** (puede ir en el
   contacto como default y/o en la cotización).
8. **Código postal fiscal** — **nuevo campo** en empresa/contacto (distinto del
   CP de entrega).
9. **Tipo de persona** (física/moral) — **nuevo campo**.
10. Por **producto**: **clave SAT producto/servicio** (`satProductKey`) y
    **clave de unidad** (`satUnitKey`) — **nuevos campos**.

---

## 4. Cambios propuestos al modelo (para no rehacer en Etapa 2)

> Solo definición de campos; se pueden agregar ya como opcionales sin afectar el
> flujo actual. La captura sería en los formularios de empresa, contacto y
> producto.

```txt
companies   + rfc (ya existe) + fiscalName (ya existe)
            + fiscalRegimeCode      # régimen SAT (601, 612, 626, …)
            + cfdiUse               # uso CFDI default (G03, S01, …)
            + fiscalZipCode         # CP fiscal
            + kindOfPerson          # 'fisica' | 'moral'
            + alegraId              # mapeo

contacts    + rfc                   # persona física
            + fiscalRegimeCode
            + cfdiUse
            + fiscalZipCode
            + kindOfPerson
            + alegraId

products    + satProductKey         # clave producto/servicio SAT
            + satUnitKey            # clave de unidad SAT
            + alegraId              # mapeo (o external_refs_json.alegra)

quotes      + expiresAt             # vigencia (dueDate de Alegra)
            + alegraEstimateId      # mapeo
```

Config global de integración (no por registro):
`ALEGRA_*` (credenciales) + `alegraIvaTaxId` (id del impuesto IVA 16%).

---

## 5. Resumen ejecutivo

- La cotización Alegra **necesita cliente e ítems ya creados** en Alegra → el CRM
  debe **guardar los ids de Alegra** y sincronizar antes de cotizar.
- Para **solo enviar la cotización**: faltan `expiresAt`, los `alegraId` de
  contacto/producto/cotización y el mapeo del impuesto.
- Para que la cotización **se pueda facturar (CFDI 4.0)**: faltan **RFC, régimen
  fiscal, uso CFDI, CP fiscal y tipo de persona** del cliente, y **claves SAT de
  producto y de unidad** en cada producto.
- Lo más urgente de empezar a **capturar desde ya** (porque depende del humano):
  RFC, régimen fiscal, uso CFDI, CP fiscal y tipo de persona en empresas/contactos;
  y claves SAT en productos.

## Fuentes
- [Crear cotización (estimates)](https://developer.alegra.com/reference/post_estimates)
- [Crear contacto](https://developer.alegra.com/reference/post_contacts)
- [Crear ítem/producto](https://developer.alegra.com/reference/post_items)
- [Cotizaciones (guía)](https://developer.alegra.com/docs/cotizaciones-1.md)
- [Catálogo de parámetros para México](https://developer.alegra.com/reference/méxico.md)
- [Regímenes fiscales en México (Alegra)](https://ayuda.alegra.com/int/regimenes-fiscales-mex)
- [CFDI 4.0 en Alegra](https://ayuda.alegra.com/int/ya-esta-disponible-la-versi%C3%B3n-4.0-del-cfdi-en-alegra-m%C3%A9xico)
