# Referencia completa de API

Contrato observado en las rutas registradas por Laravel al 21 de julio de 2026.

## Convenciones

- URL base: `https://<host>/api/v1`.
- Formato predeterminado: `application/json`.
- Los endpoints marcados como **Bearer** requieren `Authorization: Bearer <token>`.
- Las fechas de citas se interpretan con `America/Mexico_City`, salvo que el valor ISO incluya desplazamiento.
- Los IDs son enteros positivos.
- Los endpoints heredados normalmente responden HTTP `200` y colocan el código lógico dentro de `code`.

Respuesta estándar heredada:

```json
{
    "status": "OK",
    "info": { "name": "Fraccionamientos Colima" },
    "data": {},
    "code": 200
}
```

Error estándar:

```json
{
    "status": "ERROR",
    "info": { "name": "Fraccionamientos Colima" },
    "error": [{ "campo": ["Mensaje de validación"] }]
}
```

Errores habituales: `401` sin autenticación, `403` sin permisos, `404` si el recurso no existe o no pertenece al agente y `422` por validación.

## Autenticación y configuración

### POST `/login` — Público

Request:

```json
{ "email": "agente@example.com", "password": "secret" }
```

`email` también admite el nombre de usuario.

Response:

```json
{
    "status": "OK",
    "data": {
        "user": { "id": 10, "role_id": 3, "email": "agente@example.com" },
        "token": "oauth-access-token"
    },
    "code": { "code": 200, "Text": "Login correctamente" }
}
```

### POST `/register` — Público

Request:

```json
{
    "role_id": 3, //Forzado en app de agentes
    "username": "ana.lopez",
    "first_name": "Ana",
    "last_name": "López",
    "phone": "3121234567",
    "email": "ana@example.com",
    "password": "secret"
}
```

Requeridos: `role_id`, `username` único, `first_name`, `last_name`, `email` único y válido, `password`. Response: `{ "data": { "user": <User>, "token": "..." } }`.

### POST `/forget_password` — Público

Request: `{ "email": "ana@example.com" }`.

Response:

```json
{
    "data": {
        "message": "Si la cuenta existe, enviaremos un enlace para restablecer la contraseña."
    }
}
```

La respuesta no revela si el correo existe. Puede responder `429` cuando se excede el límite de solicitudes.

### POST `/reset_password` — Público

Request:

```json
{
    "email": "ana@example.com",
    "token": "token-recibido-por-correo",
    "password": "nueva-clave-segura",
    "password_confirmation": "nueva-clave-segura"
}
```

La contraseña debe tener al menos ocho caracteres. Response:

```json
{ "data": { "message": "Contraseña actualizada correctamente." } }
```

Un token inválido o vencido responde `422`. El enlace usa `FRONTEND_URL/reset-password` o, en su ausencia, `APP_URL/reset-password`.

### POST `/create_user` — Bearer

Request:

```json
{
    "role_id": 3,
    "username": "nuevo.agente",
    "first_name": "Nuevo",
    "last_name": "Agente",
    "phone": "3120000000",
    "email": "nuevo@example.com",
    "password": "secret"
}
```

Response: `{ "data": { "admin": <User> } }`. Es un endpoint heredado sin reglas de validación declaradas.

### POST `/role` — Bearer

Request: `{ "name": "Asesor", "description": "Asesor inmobiliario" }`.

Response: `{ "data": { "role": { "id": 7, "name": "Asesor", "description": "Asesor inmobiliario" } } }`.

### GET `/sorts` — Bearer

Request: sin body.

Response:

```json
{
    "data": {
        "sorts": [
            { "id": 1, "name": "Más cercanos" },
            { "id": 2, "name": "Más baratos" },
            { "id": 3, "name": "Más caros" },
            { "id": 4, "name": "Más nuevos" }
        ]
    }
}
```

## Agentes, prospectos, citas y apartado

### GET `/agents` — Público

Request: sin body. Response: `data` contiene agentes con rol `3` vinculados a desarrollos:

```json
{ "data": [{ "id": 10, "email": "agente@example.com" }] }
```

### POST `/agent-flow/prospects` — Bearer

Registra un prospecto básico y fuerza `lead_agent_id` al usuario autenticado.

Request:

```json
{
    "first_name": "Ana",
    "last_name": "López",
    "phone": "3121234567",
    "email": "ana@example.com",
    "source": "WhatsApp",
    "notes": "Solicitó información"
}
```

Requeridos: `first_name`, `phone`. Response:

```json
{
    "data": {
        "prospect": {
            "id": 81,
            "first_name": "Ana",
            "last_name": "López",
            "phone": "3121234567",
            "email": "ana@example.com",
            "source": "WhatsApp",
            "notes": "Solicitó información",
            "lead_agent_id": 10,
            "profile_complete": false
        },
        "next_action": "schedule_appointment"
    }
}
```

### POST `/agent-flow/prospects/{prospect}/appointments` — Bearer

Request:

```json
{
    "development_id": 15,
    "appointment_date": "2026-07-25T11:30:00-06:00",
    "sale_closer_agent_id": 8,
    "notes": "Visita a casa muestra"
}
```

Requeridos: `development_id`, `appointment_date`. El prospecto debe pertenecer al agente y el desarrollo a su inmobiliaria. Response:

```json
{
    "data": {
        "appointment": {
            "id": 44,
            "prospect_id": 81,
            "customer_name": "Ana López",
            "customer_phone": "3121234567",
            "customer_email": "ana@example.com",
            "development_id": 15,
            "development_name": "Senderos",
            "appointment_date": "2026-07-25 11:30:00",
            "status": "scheduled",
            "status_label": "Programada",
            "notes": "Visita a casa muestra",
            "lead_agent_id": 10,
            "sale_closer_agent_id": 8
        },
        "next_actions": ["reserve_lot", "view_another_property"]
    }
}
```

### POST `/agent-flow/prospects/{prospect}/interests` — Bearer

Request:

```json
{ "target_type": "development", "target_id": 18, "notes": "Prefiere el norte" }
```

`target_type`: `development`, `lot` o `property`. Response:

```json
{
    "data": {
        "interest": {
            "id": 9,
            "prospect_id": 81,
            "target_type": "development",
            "target_id": 18
        },
        "next_action": "schedule_appointment"
    }
}
```

### POST `/agent-flow/prospects/{prospect}/reservations` — Bearer

Completa el perfil y aparta uno o varios lotes en una sola transacción.

Request:

```json
{
    "development_id": 15,
    "lot_ids": [120, 121],
    "reservation_amounts": { "120": 10000, "121": 12000 },
    "payment_method": "Transferencia",
    "payment_reference": "SPEI-12345",
    "notes": "Apartado durante visita",
    "prospect": {
        "first_name": "Ana",
        "last_name": "López Pérez",
        "phone": "3121234567",
        "email": "ana@example.com",
        "birth_day": "1990-05-10",
        "birth_place": "Colima",
        "marital_status": "Casada",
        "occupation": "Arquitecta",
        "unofficial_manager": null,
        "beneficiary": "Luis Pérez",
        "ine_file": "https://files.example.com/ine.pdf",
        "address": {
            "street": "Av. Constitución 100",
            "neighborhood": "Centro",
            "zip_code": "28000",
            "country_id": 1,
            "state_id": 6,
            "city_id": 25
        }
    }
}
```

Requeridos: desarrollo, al menos un lote, monto positivo para cada lote, nombre, apellidos, teléfono, correo y calle. Response:

```json
{
    "data": {
        "prospect": {
            "id": 81,
            "profile_complete": true,
            "address": { "id": 40, "street": "Av. Constitución 100" }
        },
        "reservation": {
            "development_id": 15,
            "lot_ids": [120, 121],
            "payment_ids": [901, 902]
        },
        "next_action": "reservation_completed"
    }
}
```

### GET `/appointments?date={YYYY-MM-DD}&status={status}` — Bearer

Request de ejemplo: `GET /appointments?date=2026-07-25&status=confirmed`. `date` es obligatorio; `status` es opcional.

Response:

```json
{
    "data": {
        "agent_id": 10,
        "date": "2026-07-25",
        "timezone": "America/Mexico_City",
        "count": 1,
        "appointments": [
            {
                "id": 44,
                "prospect_id": 81,
                "customer_name": "Ana López",
                "customer_phone": "3121234567",
                "customer_email": "ana@example.com",
                "development_id": 15,
                "development_name": "Senderos",
                "appointment_date": "2026-07-25 11:30:00",
                "status": "confirmed",
                "status_label": "Confirmada",
                "status_tone": "positive",
                "notes": null,
                "lead_agent_id": 10,
                "lead_agent_name": "Ana Asesora",
                "sale_closer_agent_id": 8,
                "sale_closer_agent_name": "Carlos Cerrador"
            }
        ]
    }
}
```

Los agentes sólo reciben citas con su propio `lead_agent_id`.

### GET `/appointment-statuses` — Bearer

Request: sin body. Response:

```json
{
    "data": {
        "statuses": [
            { "value": "scheduled", "label": "Programada", "tone": "info" },
            { "value": "confirmed", "label": "Confirmada", "tone": "positive" },
            {
                "value": "completed",
                "label": "Completada",
                "tone": "completed"
            },
            { "value": "cancelled", "label": "Cancelada", "tone": "danger" }
        ]
    }
}
```

### PATCH `/appointment/{appointment}/status` — Bearer

Request: `{ "status": "completed" }`. También acepta `programada`, `confirmada`, `realizada` y `cancelada` y equivalentes ingleses.

Response: `{ "data": { "appointment": <Appointment> } }`. Un agente sólo puede modificar su propia cita.

### Endpoints heredados de citas — Bearer + permiso de citas

| Método y ruta                        | Request                           | Response                                                |
| ------------------------------------ | --------------------------------- | ------------------------------------------------------- |
| `GET /development/{id}/appointments` | Sin body; `{id}` es el desarrollo | `{ "data": { "appointments": [<LegacyAppointment>] } }` |
| `GET /appointment/{id}`              | Sin body                          | `{ "data": { "appointment": <LegacyAppointment> } }`    |
| `POST /development/{id}/appointment` | Ver payload inferior              | `{ "data": { "appointment": <LegacyAppointment> } }`    |

Request de creación heredada:

```json
{
    "lead_id": 81,
    "customer_name": "Ana López",
    "customer_phone": "3121234567",
    "customer_email": "ana@example.com",
    "appointment_date": "25/07/2026 11:30 AM",
    "status": "confirmada",
    "notes": "Visita",
    "sale_closer_agent_id": 8,
    "lead_agent_id": 10
}
```

`customer_name`, `customer_phone`, `appointment_date` y `status` son requeridos. `LegacyAppointment` contiene `id`, `lead_id`, datos del cliente, `appointment_date`, `status`, `notes`, `development_id`, `sale_closer_agent_id` y `lead_agent_id`.

### CRUD heredado de prospectos — Bearer

| Método y ruta                  | Request                                 | Response                                |
| ------------------------------ | --------------------------------------- | --------------------------------------- |
| `GET /prospects`               | Sin body                                | `{ "data": { "prospects": [<Lead>] } }` |
| `POST /prospect`               | `<LegacyLeadInput>`                     | `{ "data": { "prospect": <Lead> } }`    |
| `GET /prospect/{id}`           | Sin body                                | `{ "data": { "prospect": <Lead> } }`    |
| `POST /prospect/{id}/update`   | Campos parciales de `<LegacyLeadInput>` | `{ "data": { "prospect": <Lead> } }`    |
| `DELETE /prospect/{id}/delete` | Sin body                                | Prospecto eliminado en `data.prospect`  |

`LegacyLeadInput`:

```json
{
    "first_name": "Ana",
    "last_name": "López",
    "birthDay": "1990-05-10",
    "birthPlace": "Colima",
    "maritalStatus": "Casada",
    "occupation": "Arquitecta",
    "unofficialManager": null,
    "beneficiary": "Luis Pérez",
    "email": "ana@example.com",
    "phone": "3121234567",
    "source": "Referido",
    "status": 1,
    "pipeline": 1,
    "ine_file": "https://files.example.com/ine.pdf",
    "image": "https://files.example.com/photo.jpg",
    "lead_agent_id": 10,
    "notes": "Observaciones"
}
```

En rol agente, `lead_agent_id` es forzado al usuario autenticado y todos los accesos se filtran por ese agente.

### Actividades de prospectos — Bearer

| Método y ruta                   | Request                                                         | Response                                                             |
| ------------------------------- | --------------------------------------------------------------- | -------------------------------------------------------------------- |
| `GET /prospect/{id}/activities` | Sin body                                                        | `{ "data": { "activities": [<Activity>] } }`                         |
| `POST /prospect/{id}/activity`  | `{ "lead_id": 81, "lead_agent_id": 10, "activity_type_id": 2 }` | `{ "data": { "activity": <Activity> } }`                             |
| `GET /activity/{id}`            | Sin body                                                        | `{ "data": { "activity": <Activity> } }`                             |
| `POST /activity/{id}/update`    | `{ "activity": "Texto heredado" }`                              | `{ "data": { "activity": <Activity> } }`                             |
| `DELETE /activity/{id}/delete`  | Sin body                                                        | Actividad eliminada en `data.activity`                               |
| `GET /activity_types`           | Sin body                                                        | `{ "data": { "activity_types": [{ "id": 2, "name": "Llamada" }] } }` |

## Cotizaciones

### POST `/quotes/calculate` — Bearer

Request:

```json
{
    "lot_ids": [120, 121],
    "payment_plan_id": 4,
    "down_payment": 50000,
    "installments": 36
}
```

`lot_ids` es requerido, no admite duplicados. Los demás campos son opcionales. Response HTTP `200`:

```json
{
    "status": "OK",
    "message": "Cotización calculada correctamente.",
    "data": {
        "lots": [
            {
                "id": 120,
                "development_id": 15,
                "lote_type_id": 2,
                "block": "A",
                "lot": "12",
                "area_sqm": 200,
                "price_per_sqm": 1750,
                "subtotal": 350000,
                "discount": 0,
                "total": 350000,
                "currency": "MXN"
            }
        ],
        "summary": {
            "lots_count": 1,
            "total_area_sqm": 200,
            "subtotal": 350000,
            "discount": 0,
            "total": 350000,
            "minimum_down_payment": 10000,
            "suggested_down_payment": 35000,
            "selected_down_payment": 50000,
            "financed_amount": 300000,
            "interest_amount": 0,
            "financed_total": 300000,
            "installments": 36,
            "installment_amount": 8333.33,
            "currency": "MXN"
        },
        "payment_plan": { "id": 4, "name": "36 meses" },
        "rules": {
            "down_payment_editable": true,
            "installments_editable": true,
            "minimum_installments": 1,
            "default_installments": 36,
            "maximum_installments": 36,
            "down_payment_strategy": "fixed",
            "interest_type": null,
            "interest_rate": 0,
            "interest_free": true,
            "max_discount_percent": 0,
            "expires_at": null
        }
    }
}
```

Puede responder `409` si cambia el inventario o `422` si los lotes/condiciones no son válidos.

### POST `/quotes` — Bearer

Usa el mismo request y response de cálculo, añade:

```json
{ "quote": { "id": 30, "uuid": "...", "status": "draft", "expires_at": null } }
```

Responde HTTP `201` y persiste la cotización.

## Inmobiliarias y sucursales

### Inmobiliarias — Bearer

| Método y ruta                     | Request                                                                                                         | Response                                                         |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ---------- |
| `GET /real_estates`               | Sin body                                                                                                        | `{ "data": { "real_estate_agencies": [<AgencyWithBranches>] } }` |
| `POST /create_real_estate`        | `{ "name": "Tejuino", "email": "ventas@example.com", "phone": "3120000000", "website": "https://example.com" }` | `{ "data": { "real_estate_agency": <Agency> } }`                 |
| `GET /real_estate/{id}`           | Sin body                                                                                                        | `{ "data": { "real_estate_agency": <Agency                       | null> } }` |
| `POST /real_estate/{id}/update`   | Mismos campos que creación                                                                                      | Agencia actualizada                                              |
| `DELETE /real_estate/{id}/delete` | Sin body                                                                                                        | Agencia eliminada                                                |
| `GET /user/{id}/real_estates`     | Sin body; el usuario consulta su propio ID y un administrador puede consultar cualquier ID                      | `{ "data": { "real_estate_agencies": [<Agency>] } }`             |

La creación está limitada por código al rol de inmobiliaria (`role_id = 2`). Los demás endpoints son heredados y no declaran autorización por recurso.

### Sucursales — Bearer

| Método y ruta                                      | Request                                                                      | Response                                             |
| -------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------- | ---------- |
| `GET /real_estate/{id}/real_estate_branches`       | Sin body                                                                     | `{ "data": { "real_estate_branches": [<Branch>] } }` |
| `POST /real_estate/{id}/create_real_estate_branch` | Ver payload inferior                                                         | `{ "data": { "real_estate_branch": <Branch> } }`     |
| `GET /real_estate_branch/{id}`                     | Sin body                                                                     | `{ "data": { "real_estate_branch": <Branch           | null> } }` |
| `POST /real_estate_branch/{id}/update`             | `{ "name": "Centro", "email": "centro@example.com", "phone": "3120000001" }` | Sucursal en `data.real_estate_branch`                |
| `DELETE /real_estate_branch/{id}/delete`           | Sin body                                                                     | Sucursal eliminada                                   |

Creación de sucursal:

```json
{
    "name": "Centro",
    "email": "centro@example.com",
    "phone": "3120000001",
    "address": {
        "street": "Av. Principal 10",
        "neighborhood": "Centro",
        "country_id": 1,
        "state_id": 6,
        "city_id": 25,
        "zip_code": "28000"
    }
}
```

La actualización persiste `name`, `email` y `phone`.

## Catálogos geográficos — Públicos

Todos estos endpoints están registrados sin autenticación.

| Método y ruta                     | Request                                       | Response                                   |
| --------------------------------- | --------------------------------------------- | ------------------------------------------ | ---------- |
| `GET /countries`                  | Sin body                                      | `{ "data": { "countries": [<Country>] } }` |
| `POST /create_country`            | `{ "name": "México", "code": "MX" }`          | `{ "data": { "country": <Country> } }`     |
| `GET /country/{id}`               | Sin body                                      | `{ "data": { "country": <Country           | null> } }` |
| `POST /country/{id}/update`       | `{ "name": "México", "code": "MX" }`          | País actualizado                           |
| `DELETE /country/{id}/delete`     | Sin body                                      | País eliminado                             |
| `GET /country/{id}/states`        | Sin body                                      | `{ "data": { "states": [<State>] } }`      |
| `POST /country/{id}/create_state` | `{ "name": "Colima", "abbreviation": "COL" }` | `{ "data": { "state": <State> } }`         |
| `GET /state/{id}`                 | Sin body                                      | `{ "data": { "state": <State               | null> } }` |
| `POST /state/{id}/update`         | `{ "name": "Colima", "abbreviation": "COL" }` | Estado actualizado                         |
| `DELETE /state/{id}/delete`       | Sin body                                      | Estado eliminado                           |
| `GET /state/{id}/cities`          | Sin body                                      | `{ "data": { "cities": [<City>] } }`       |
| `POST /state/{id}/create_city`    | `{ "name": "Villa de Álvarez" }`              | `{ "data": { "city": <City> } }`           |
| `GET /city/{id}`                  | Sin body                                      | `{ "data": { "city": <City                 | null> } }` |
| `POST /city/{id}/update`          | `{ "name": "Colima" }`                        | Ciudad actualizada                         |
| `DELETE /city/{id}/delete`        | Sin body                                      | Ciudad eliminada                           |

## Tipos de lote y planes de pago — Bearer

### Tipos de lote

| Método y ruta                   | Request                                                        | Response                                     |
| ------------------------------- | -------------------------------------------------------------- | -------------------------------------------- | ---------- |
| `GET /lote_types`               | Sin body                                                       | `{ "data": { "lote_types": [<LoteType>] } }` |
| `POST /create_lote_type`        | `{ "name": "Residencial", "description": "Lote residencial" }` | `{ "data": { "lote_type": <LoteType> } }`    |
| `GET /lote_type/{id}`           | Sin body                                                       | `{ "data": { "lote_type": <LoteType          | null> } }` |
| `POST /lote_type/{id}/update`   | Mismo request de creación                                      | Tipo actualizado                             |
| `DELETE /lote_type/{id}/delete` | Sin body                                                       | Tipo eliminado                               |

### Planes de pago

| Método y ruta                      | Request                                                                          | Response                                           |
| ---------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------- | ---------- |
| `GET /payment_plans`               | Sin body                                                                         | `{ "data": { "payment_plans": [<PaymentPlan>] } }` |
| `POST /create_payment_plan`        | `{ "name": "36 meses", "description": "Plan a 3 años", "financing_months": 36 }` | `{ "data": { "payment_plan": <PaymentPlan> } }`    |
| `GET /payment_plan/{id}`           | Sin body                                                                         | `{ "data": { "payment_plan": <PaymentPlan          | null> } }` |
| `POST /payment_plan/{id}/update`   | Mismo request de creación                                                        | Plan actualizado                                   |
| `DELETE /payment_plan/{id}/delete` | Sin body                                                                         | Plan eliminado                                     |

## Dashboard y unidades — Bearer

### GET `/dashboard`

Query opcional:

```text
?search=senderos&property_type[]=1&price_min=100000&price_max=900000&state=Colima&city=Colima&neighborhood=Centro&radius_km=10&limit=20
```

`limit` admite `1..50`. Response:

```json
{
    "data": {
        "properties": [
            {
                "category": { "id": 1, "name": "Desarrollos" },
                "units": [
                    {
                        "id": 15,
                        "name": "Senderos",
                        "description": "...",
                        "image": "..."
                    }
                ]
            }
        ],
        "prospects": [
            {
                "id": 21,
                "first_name": "Ana",
                "last_name": "López",
                "full_name": "Ana López",
                "phone": "3121234567",
                "email": "ana@example.com",
                "source": "Referido",
                "status": "Nuevo",
                "lead_agent_id": 10,
                "last_activity_at": "2026-07-26 09:30:00",
                "created_at": "2026-07-25 13:00:00"
            }
        ],
        "appointments": [
            {
                "id": 8,
                "prospect_id": 21,
                "customer_name": "Ana López",
                "customer_phone": "3121234567",
                "development_id": 15,
                "development_name": "Senderos",
                "appointment_date": "2026-07-26 11:00:00",
                "status": "scheduled",
                "status_label": "Programada"
            }
        ]
    }
}
```

`prospects` devuelve como máximo los 5 prospectos más recientemente actualizados. `appointments` contiene las citas no canceladas del día actual, ordenadas por hora. Ambos listados respetan el alcance del usuario autenticado.

### GET `/unit/{id}?category={category}`

`category` requerido: `1` desarrollo, `2` lote, `3` casa. Response: `data` contiene el detalle estructurado de la unidad; para desarrollos incluye `geojson_url` cuando aplica.

```json
{
    "data": {
        "id": 15,
        "category": 1,
        "name": "Senderos",
        "geojson_url": "https://<host>/api/v1/unit/15/geojson"
    }
}
```

### GET `/unit/{id}/geojson`

Request: sin body. Response: contenido GeoJSON de `storage/app/public/maps/lotes.geojson`; `404` si no existe. Actualmente `{id}` no altera el archivo servido.

## Fraccionamientos y lotes — Bearer

### GET `/developments`

Query opcional: `per_page` (`1..100`) y `page` (`>=1`). Sin esos parámetros devuelve todos los desarrollos. Response:

```json
{
    "data": {
        "developments": [
            {
                "id": 15,
                "name": "Senderos",
                "slug": "senderos",
                "currency": "MXN",
                "location": { "lat": 19.2, "lng": -103.7 }
            }
        ]
    }
}
```

### POST `/development`

Acepta formato plano o agrupado. Request recomendado:

```json
{
    "general": {
        "real_estate_branch_id": 4,
        "development_type_id": 2,
        "development_status_id": 1,
        "slug": "senderos",
        "name": "Senderos",
        "logo": "https://files.example.com/logo.png",
        "blueprint": "https://files.example.com/map.geojson",
        "geojson_url": "https://files.example.com/map.geojson",
        "lat": 19.2433,
        "lng": -103.7241,
        "start_date": "2026-01-01",
        "end_date": "2028-12-31",
        "sort_description": "Lotes residenciales",
        "full_description": "Descripción completa",
        "image": "https://files.example.com/banner.jpg",
        "currency": "MXN",
        "price_mode": "per_sqm",
        "pricing_note": "Precios sujetos a cambio",
        "location_references": "A 5 min del centro",
        "property_regime": "Propiedad privada",
        "deed_available": true,
        "contract_type": "Compraventa",
        "notary_certified": true,
        "legal_notes": null,
        "amenity_ids": [1, 2],
        "address": {
            "street": "Carretera 100",
            "city_id": 25,
            "state_id": 6,
            "country_id": 1
        }
    },
    "settings": {
        "annuity": {},
        "payment": {},
        "paymentPlans": [],
        "mortgageCredits": [],
        "propertyTypes": [],
        "houseModels": []
    },
    "lots": [
        {
            "block": "A",
            "lot": "12",
            "area": 200,
            "price_per_sqm": 1750,
            "price_is_manual": false,
            "type": "Residencial",
            "status": "DISPONIBLE",
            "geojson_index": 0,
            "geojson_feature_id": "lot-12",
            "is_agency_owned": false,
            "agency_financing_label": null,
            "agency_down_payment": null,
            "agency_financing_months": null,
            "agency_monthly_payment": null
        }
    ]
}
```

Requeridos: sucursal, tipo de desarrollo y nombre. Response: objeto `DevelopmentData` con inmobiliaria, sucursal, tipo, ubicación, datos legales y amenidades.

### GET y edición heredada de desarrollo

| Método y ruta                 | Request                                      | Response                                                    |
| ----------------------------- | -------------------------------------------- | ----------------------------------------------------------- |
| `GET /development/{id}`       | Sin body                                     | `{ "data": { "development": <DevelopmentWithRelations> } }` |
| `POST /development/{id}/edit` | `multipart/form-data`, ver campos inferiores | `{ "data": { "development": <Development> } }`              |

Campos de edición: `name`, `logo`, archivo `blueprint`, `location`, `total_land_area`, `total_lotes`, `available_lotes`, `start_date`, `end_date`, `sort_description`, `full_description`, `status` y archivo `image`.

### Tipos y planes asignados al desarrollo

| Método y ruta                                                                     | Request                                                                  | Response                                                    |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------- |
| `GET /development/{id}/lote_types`                                                | Sin body                                                                 | `{ "data": { "lote_types": [<LoteTypeWithPrice>] } }`       |
| `POST /development/{id}/assign_lote_type`                                         | `{ "lote_type_id": 2, "price": 1750 }`                                   | Desarrollo en `data.development`                            |
| `GET /development/{id}/payment_plans`                                             | Sin body                                                                 | `{ "data": { "payment_plans": [<PaymentPlanWithPrice>] } }` |
| `POST /development/{development_id}/lote_type/{lote_type_id}/assign_payment_plan` | `{ "payment_plan_id": 4, "price_per_sqm": 1750, "down_payment": 10000 }` | Desarrollo en `data.development`                            |

### Lotes

| Método y ruta                             | Request                                                                           | Response                                        |
| ----------------------------------------- | --------------------------------------------------------------------------------- | ----------------------------------------------- | ---------- |
| `GET /development/{development_id}/lotes` | Query opcional `available_only=true`                                              | `{ "data": { "lotes": [<LotEstimate>] } }`      |
| `POST /development/{development_id}/lote` | Ver payload inferior                                                              | Un lote en `data.lote` o varios en `data.lotes` |
| `GET /lote/{id}`                          | Sin body                                                                          | `{ "data": { "Lote": <Lote                      | null> } }` |
| `GET /development/{id}/lote/price`        | Query `lote`, opcionales `payment_plan`, `down_payment`, `annuity`, `mensualidad` | `{ "data": { "price_info": <PriceInfo> } }`     |
| `POST /lote/{id}/metadata`                | `{ "key": "frente", "value": "10m" }`                                             | `{ "data": { "metadata": [<Metadata>] } }`      |

Creación de lote:

```json
{
    "lote_type_id": 2,
    "isMultiLote": 0,
    "lote_number": "12",
    "block_number": "A",
    "lote_size": 200
}
```

Para múltiples lotes use `isMultiLote: 1` y `lote_number: "12,13,14"`.

`LotEstimate` incluye identificación, desarrollo, tipo/estatus, manzana, número, superficie, fuente de inventario, precio y plan predeterminado. `PriceInfo` contiene precios formateados, enganche, financiamiento, mensualidad, anualidad y texto comercial.

## Mapa público de lotes

### GET `/public/developments/{development}/lot-map` — Público

`{development}` acepta ID o slug. Request: sin body. Response nativa, sin sobre:

```json
{
    "development": {
        "id": 15,
        "slug": "senderos",
        "name": "Senderos",
        "description": "..."
    },
    "stats": { "total": 100, "available": 60 },
    "map": { "lots": [] }
}
```

### GET `/public/developments/{development}/lot-map.geojson` — Público

Request: sin body. Response `application/geo+json` con caché pública de 300 segundos:

```json
{
    "type": "FeatureCollection",
    "features": [
        { "type": "Feature", "properties": { "lot_id": 120 }, "geometry": {} }
    ]
}
```

## Matriz de acceso

| Área                                                                                                           | Acceso                                    |
| -------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| Login, registro, agentes, geografía, mapa público                                                              | Público                                   |
| Flujo comercial, citas, prospectos, cotizaciones, dashboard, desarrollos, inmobiliarias, catálogos comerciales | Bearer                                    |
| Citas heredadas                                                                                                | Bearer + `viewAppointmentsMenu`           |
| Prospectos/citas para rol agente                                                                               | Sólo registros del propio `lead_agent_id` |
| Fraccionamientos usados por agentes                                                                            | Sólo los vinculados a su inmobiliaria     |

## Observaciones del contrato actual

- Muchos CRUD heredados no usan `FormRequest`; los campos indicados son los consumidos por el controlador, pero no todos tienen mensajes de validación controlados.
- Algunos `DELETE` son borrados lógicos cuando el modelo usa `SoftDeletes`.
- Los CRUD geográficos modifican datos y actualmente son públicos.

## Inventario exacto de operaciones registradas

Esta lista usa la URI completa que reporta Laravel y sirve como control de cobertura:

- `GET /api/v1/activity/{id}`
- `DELETE /api/v1/activity/{id}/delete`
- `POST /api/v1/activity/{id}/update`
- `GET /api/v1/activity_types`
- `POST /api/v1/agent-flow/prospects`
- `POST /api/v1/agent-flow/prospects/{prospect}/appointments`
- `POST /api/v1/agent-flow/prospects/{prospect}/interests`
- `POST /api/v1/agent-flow/prospects/{prospect}/reservations`
- `GET /api/v1/agents`
- `GET /api/v1/appointment-statuses`
- `PATCH /api/v1/appointment/{appointment}/status`
- `GET /api/v1/appointment/{id}`
- `GET /api/v1/appointments`
- `GET /api/v1/city/{id}`
- `DELETE /api/v1/city/{id}/delete`
- `POST /api/v1/city/{id}/update`
- `GET /api/v1/countries`
- `GET /api/v1/country/{id}`
- `POST /api/v1/country/{id}/create_state`
- `DELETE /api/v1/country/{id}/delete`
- `GET /api/v1/country/{id}/states`
- `POST /api/v1/country/{id}/update`
- `POST /api/v1/create_country`
- `POST /api/v1/create_lote_type`
- `POST /api/v1/create_payment_plan`
- `POST /api/v1/create_real_estate`
- `POST /api/v1/create_user`
- `GET /api/v1/dashboard`
- `POST /api/v1/development`
- `POST /api/v1/development/{development_id}/lote`
- `POST /api/v1/development/{development_id}/lote_type/{lote_type_id}/assign_payment_plan`
- `GET /api/v1/development/{development_id}/lotes`
- `GET /api/v1/development/{id}`
- `POST /api/v1/development/{id}/appointment`
- `GET /api/v1/development/{id}/appointments`
- `POST /api/v1/development/{id}/assign_lote_type`
- `POST /api/v1/development/{id}/edit`
- `GET /api/v1/development/{id}/lote/price`
- `GET /api/v1/development/{id}/lote_types`
- `GET /api/v1/development/{id}/payment_plans`
- `GET /api/v1/developments`
- `POST /api/v1/forget_password`
- `POST /api/v1/login`
- `GET /api/v1/lote/{id}`
- `POST /api/v1/lote/{id}/metadata`
- `GET /api/v1/lote_type/{id}`
- `DELETE /api/v1/lote_type/{id}/delete`
- `POST /api/v1/lote_type/{id}/update`
- `GET /api/v1/lote_types`
- `GET /api/v1/payment_plan/{id}`
- `DELETE /api/v1/payment_plan/{id}/delete`
- `POST /api/v1/payment_plan/{id}/update`
- `GET /api/v1/payment_plans`
- `POST /api/v1/prospect`
- `GET /api/v1/prospect/{id}`
- `GET /api/v1/prospect/{id}/activities`
- `POST /api/v1/prospect/{id}/activity`
- `DELETE /api/v1/prospect/{id}/delete`
- `POST /api/v1/prospect/{id}/update`
- `GET /api/v1/prospects`
- `GET /api/v1/public/developments/{development}/lot-map`
- `GET /api/v1/public/developments/{development}/lot-map.geojson`
- `POST /api/v1/quotes`
- `POST /api/v1/quotes/calculate`
- `GET /api/v1/real_estate/{id}`
- `POST /api/v1/real_estate/{id}/create_real_estate_branch`
- `DELETE /api/v1/real_estate/{id}/delete`
- `GET /api/v1/real_estate/{id}/real_estate_branches`
- `POST /api/v1/real_estate/{id}/update`
- `GET /api/v1/real_estate_branch/{id}`
- `DELETE /api/v1/real_estate_branch/{id}/delete`
- `POST /api/v1/real_estate_branch/{id}/update`
- `GET /api/v1/real_estates`
- `POST /api/v1/register`
- `POST /api/v1/reset_password`
- `POST /api/v1/role`
- `GET /api/v1/sorts`
- `GET /api/v1/state/{id}`
- `GET /api/v1/state/{id}/cities`
- `POST /api/v1/state/{id}/create_city`
- `DELETE /api/v1/state/{id}/delete`
- `POST /api/v1/state/{id}/update`
- `GET /api/v1/unit/{id}`
- `GET /api/v1/unit/{id}/geojson`
- `GET /api/v1/user/{id}/real_estates`
