{"openapi":"3.1.0","info":{"title":"Pitflo · API pública /v1","version":"2026-09-23","description":"API pública de solo lectura de TU empresa en Pitflo. Cada empresa arma su propia integración desde su pantalla de Integraciones: elige recursos y campos (negado por omisión: nada nace expuesto) y emite un token `pfk_` que se enseña UNA sola vez.\n\nReglas del contrato:\n- Solo `GET`. La escritura por API no existe en v1. Las BAJAS sí te llegan en los recursos que ya tienen lápida: `customer.deleted`, `vehicle.deleted` y `service.deleted` te avisan con `{ id, deleted: true }` para que quites esa fila de tu copia (`service.restored` deshace la baja de la orden y viaja con la fila completa). En los demás recursos el borrado sigue sin evento — si copias comprobantes o inventario, reconcilia con `updated_since` (documentado de frente).\n- Autenticación: `Authorization: Bearer pfk_…`. El token es de SERVIDOR: jamás en una app web o móvil. Una cookie de sesión sobre `/v1` es 403 de plano.\n- Cada respuesta trae SOLO los campos marcados en la integración; los campos con `x-pii: true` son datos personales y la pantalla los agrupa bajo advertencia.\n- El contrato entero está en INGLÉS: nombres de recursos, de campos, de filtros y códigos de error. Esta prosa es lo único bilingüe (`GET /v1/openapi.json?lang=en`).\n- Paginación por cursor firmado y opaco (`{ data, cursor }`; `cursor: null` = no hay más). El cursor está atado a tu token, al recurso y a los filtros: no es portable.\n- `updated_since` filtra por `updatedAt >= …`; en los libros append-only (`service_payments`, `expense_payments`, `inventory_movements`, `cash_movements`, `service_stage_transitions`), por `createdAt`. Deduplica por `id`: el filtro es `gte` a propósito.\n- Límite: 600 peticiones/min por token; sin token válido, cubo estricto por IP. 429 responde con `Retry-After`.\n- Dinero SIEMPRE en centavos, tal cual la base; el consumidor formatea. Las cantidades de inventario van en MILÉSIMAS de la unidad base (1000 = 1 pieza/litro).\n- Errores en formato canónico `{ code, message, details }`.\n\nUn cambio incompatible del contrato estrena `/v2`; este documento cubre `/v1` y se acompaña del changelog (`x-changelog`).","x-changelog":[{"fecha":"2026-09-23","cambios":["LA REFACCIÓN SOBRE PEDIDO ENTRA A /v1. Desde el 2026-09-22 un taller puede pedir una pieza PARA UN COCHE en su orden de compra: al recibirla sale directo a esa orden de servicio, sin pasar por el anaquel, y no cuenta en existencias, mínimos ni rotación. Hoy eso se publica en tres lugares, todos aditivos.","`products` GANA `specialOrder` (booleano): `true` = todo lo que se recibió de ese producto salió directo a las órdenes que lo pidieron y su existencia quedó en cero; se apaga solo con cualquier entrada al anaquel. Es `false` en todo producto que no haya pasado por eso.","`inventory_movements` GANA `purchaseOrderRequestId`: la partida de orden de compra que surtió ese asiento DIRECTO (la `compra` y el `consumo` del par llevan el mismo id). Es `null` en todo asiento que no vino de una OC sobre pedido —todos los anteriores a hoy—. La descripción de `issueOrigin` dice desde hoy el valor `sobre_pedido`, que ya viajaba en esos asientos.","NACE `purchase_order_requests`, solo lectura: el REPARTO de cada línea de orden de compra entre las órdenes de servicio que la pidieron (`purchaseOrderLineId`, `serviceId`, `serviceItemId`, `requestedMilli` = lo que esa orden pidió, `issuedDirectMilli` = cuánto de eso ya le llegó directo). Lo que la línea pidió y no está en ninguna fila es anaquel. Cuelga de `purchase_order_lines` y no tiene sucursal propia: se consulta con `purchase_order_line={id}` o `service={id}`; `workshop` responde 400 FILTER_NOT_SUPPORTED, como en las líneas. Pagina por `updatedAt`: la fila CAMBIA con cada recepción (`issuedDirectMilli` sube) y `updated_since` te trae esos cambios.","UN FILTRO DE RELACIÓN MÁS, con la misma regla que los demás: `purchase_order_line={id}` (en `purchase_order_requests`, el reparto de esa línea; en `purchase_order_lines`, la línea misma). Entra en la firma del cursor como todos.","Ningún campo, recurso, filtro ni evento existente cambia de nombre, tipo ni significado."],"huella":"2b78c55aefcb25c63c74409b2300edba89af53c8bd7e6d77a3bc93297520bfcf"},{"fecha":"2026-09-22","cambios":["CANCELAR UN JOB DE CARGA MASIVA SE DICE EN INGLÉS: `POST /v1/bulk/jobs/{jobId}/cancel`. Es la MISMA operación con la misma respuesta; solo cambia la última palabra de la ruta. Es lo único de /v1 que estaba en español.","`POST /v1/bulk/jobs/{jobId}/cancelar` SIGUE SIRVIENDO PARA SIEMPRE, sin fecha de retiro y sin cabecera de deprecación: /v1 es append-only y lo que ya se entregó no se quita. No tienes que cambiar nada; si prefieres la ruta nueva, cámbiala cuando quieras.","Ningún recurso, campo, filtro ni evento cambia: la huella de esta entrada es idéntica a la anterior."],"huella":"2817b0eaaed80a5944a3b797937ffa88f68b76cacb5496f11cd310cdce4472ea"},{"fecha":"2026-09-21","cambios":["LAS BAHÍAS DEL TALLER ENTRAN A LA CITA. Un taller puede dar de alta sus rampas, fosos y cabinas, y decir en cada cita qué TIPO de bahía va a necesitar el trabajo y cuál queda reservada. El taller que no las configura no cambia en nada: los dos campos viajan nulos.","`appointments` GANA `bayKind` (el tipo pedido: `rampa`, `foso`, `alineacion`, `lavado`, `diagnostico`, `hojalateria`, `pintura`, `llantas`, `patio`, `general`) y `bayId` (la bahía reservada). Reservar NO aparta la bahía físicamente: al llegar el coche, la orden nace en la cola de esa bahía y el taller la pasa adentro cuando se libera. Los dos son aditivos y nulos en toda cita anterior a hoy.","`service_templates` GANA `bayKind`: qué tipo de bahía suele necesitar ese servicio del catálogo. Es la fuente de la sugerencia en la cita —nunca un candado—, y es nulo mientras el taller no lo declare.","Las BAHÍAS EN SÍ (el mapa, quién ocupa cada una y su cola) NO son recurso de /v1 en esta entrega: lo que se publica es lo que la cita declara. Si tu integración necesita leer la ocupación, pídelo y se decide como recurso propio."],"huella":"2817b0eaaed80a5944a3b797937ffa88f68b76cacb5496f11cd310cdce4472ea"},{"fecha":"2026-09-18","cambios":["EL PRODUCTO QUE SE VENDE INSTALADO ES UNA SOLA LÍNEA. Hasta hoy, agregar un producto «con mano de obra» creaba dos líneas en la orden (`product` y una `labor` aparte); desde hoy crea UNA línea `product` cuyo importe ya trae la mano de obra dentro. El cliente final aprueba y paga una sola cosa, y en la factura es un solo concepto.","`service_items` GANA `laborIncluded` (booleano). Con `true`, el `totalCents` de la línea es cantidad × `unitPriceCents` menos su descuento MÁS `laborHours` × `laborRateCents`; el descuento de la línea se aplica solo a la pieza. Con `false` —todas las líneas anteriores a hoy, y cualquier línea que no sea un producto instalado— `totalCents` se calcula como siempre y `laborHours`/`laborRateCents` son informativos. Si reconstruyes importes desde cantidad y precio, revisa este campo: es aditivo y no cambia ninguna línea existente."],"huella":"4d2ec97823c2655289d2d79cb6b9603e0d5149bd9a31519a038b9eb3c0f563b8"},{"fecha":"2026-09-16","cambios":["LOS CONSENTIMIENTOS DEL CLIENTE FINAL, consultables por API. Cada empresa define en Pitflo sus propios TIPOS de consentimiento —`marketing`, `whatsapp`, lo que necesite— con una clave estable que ella escribe; el cliente otorga, niega o retira cada uno desde el reporte público de su orden, al agendar en línea (cuando la cita nace ligada a un cliente) o en el mostrador. No hay un vocabulario fijo de Pitflo: las claves son de cada empresa.","`customers` GANA `consents`: la lista de consentimientos sobre los que el cliente decidió, cada uno con `key`, `name`, `granted` (true SOLO con el sí vigente), `status` (`otorgado`, `negado`, `retirado`, `desactualizado` = la empresa cambió el texto desde que decidió, `vencido` = la vigencia se cumplió), `version` (la del texto que decidió), `decidedAt`, `expiresAt` (nulo = no vence) y `active` (false = el tipo dejó de pedirse; la decisión sigue valiendo). Un tipo sin decisión NO aparece: ausencia = sin respuesta. Quién decidió (nombre, teléfono, correo) NO viaja: es dato personal que no hace falta para filtrar.","`vehicles` GANA `customerConsents`: los consentimientos del cliente DUEÑO del vehículo, con la misma forma, para no tener que cruzar dos recursos.","FILTRO NUEVO `consent=<key>` (solo los clientes —o vehículos de clientes— con ese consentimiento OTORGADO y vigente) y `consent=<key>:false` (los que NO lo tienen: nunca contestaron, negaron, retiraron, venció o el texto cambió desde que aceptaron). Lo entienden `customers` y `vehicles`; en cualquier otro recurso es 400 FILTER_NOT_SUPPORTED. Una clave que la empresa no tiene es 400 `CONSENT_TYPE_NOT_FOUND` con `details.key` —jamás una lista vacía—. Entra en la firma del cursor.","NACE `consent_types` (solo lectura): el catálogo de tipos de la empresa —`key`, `name`, `version` (la vigente del texto), `validityMonths` (nulo = no vence) y `active`— para resolver las claves que `consents` publica. El texto que el cliente leyó no viaja: queda sellado en cada decisión. Es de la empresa entera: `workshop=` no aplica. El historial de decisiones NO es recurso en esta entrega (decisión explícita; se agrega si un integrador lo pide).","EVENTO NUEVO `customer.consent_changed`, por `customers`: late cada vez que un cliente otorga, niega o retira. Es el primer evento con CARGA PROPIA: `data` trae `customerId`, `key`, `decision`, `version`, `decidedAt` y `source` (`reporte`, `cita` o `mostrador`), y al lado `customer` con la fila del cliente proyectada con los campos que marcaste (si marcaste `consents`, ahí viene el estado completo). Como todo evento, solo llega si tu integración tiene `customers` marcado."],"huella":"6e7c465ff88f891f9726de7ab3635c72e4681439b32fe272cf5fb49cfe671434"},{"fecha":"2026-09-11","cambios":["NACEN CUATRO RECURSOS DE SOLO LECTURA: EL ÁRBOL DE CATEGORÍAS. `product_categories` y `product_subcategories` (cómo el taller clasifica su catálogo de refacciones) y `service_categories` y `service_subcategories` (cómo clasifica sus servicios, y la categoría por la que AGRUPA LA VENTA). Cada fila trae `workshopId` (nulo = de la empresa; con sucursal = «solo esta sucursal»), `position` (el orden del taller), `isActive`, `color` en las categorías y la procedencia de marca (`brandNetworkId`/`brandItemId`: la fila la dicta la marca de la red y el taller solo la ordena u oculta). `product_subcategories` trae además `saleCategoryId`/`saleSubcategoryId`: la categoría de VENTA del árbol de servicios a la que suma un producto de ahí cuando se vende suelto.","`service_items` GANA `saleCategoryId` y `saleSubcategoryId`: la categoría de VENTA SELLADA en cada renglón —la del paquete si el renglón cuelga de un servicio (`parentItemId`), la de su plantilla, o la de la subcategoría del producto vendido suelto—. Es lo que agrupan los reportes de ventas del taller; se sella al crear el renglón y no se reescribe en una orden ya entregada. Se resuelve contra `service_categories` / `service_subcategories`. Nula solo en renglones anteriores al sellado.","`service_templates` GANA `categoryId` y `subcategoryId` (obligatorias las dos): dónde vive la plantilla en el árbol de servicios, y la categoría de venta que heredan sus renglones. Lo anunciado el mismo día al retirar `canonicalKey`, cumplido.","DOS FILTROS DE RELACIÓN MÁS, con la regla de siempre (cada recurso declara cuáles entiende; el que no, 400 FILTER_NOT_SUPPORTED con el nombre del filtro; entran en la firma del cursor): `category={id}` y `subcategory={id}`. En `products` acotan por su clasificación de catálogo; en `service_templates`, por la suya; en `service_items`, por la categoría/subcategoría de VENTA sellada; en las listas de subcategorías, `category=` es su categoría; en las listas de categorías y subcategorías, el filtro de su propio nivel es la fila misma. El OpenAPI de cada recurso lista los suyos.","`service_subcategories` NO entiende `workshop=` (400 FILTER_NOT_SUPPORTED): la subcategoría cuelga de su categoría y el alcance de sucursal vive ahí. Consúltala con `category={id}`. Los otros tres recursos del árbol se comportan como todo catálogo con fila corporativa: `workshop={id}` resuelve la sucursal MÁS lo de la empresa.","`products` no cambia de campos; solo gana los dos filtros. Los eventos no cambian."],"huella":"1f5f505841ddbbaa3e4e58ae7f83507619c6ec5ebf165f6e49ca47a4ebd62a13"},{"fecha":"2026-09-11","cambios":["`service_templates` PIERDE `canonicalKey`. La «familia canónica» —la lista cerrada de once valores (`afinacion`, `frenos`, `otro`…) con la que se comparaba el mismo servicio entre talleres— se eliminó del producto: desde hoy toda plantilla vive en una categoría y una subcategoría del árbol de servicios del taller (obligatorias las dos, con un árbol de fábrica común a toda empresa), y ésa es la clasificación que la red y los reportes agrupan. Si tu integración leía `canonicalKey`, deja de venir: no se reemplazó por otro campo en esta entrega.","LO QUE VIENE EN LA SIGUIENTE ENTREGA, dicho hoy para que planees: `service_templates` gana `categoryId` y `subcategoryId`; `service_items` gana `saleCategoryId` y `saleSubcategoryId` (la categoría de VENTA sellada en cada renglón: la del paquete si el renglón es hijo de un servicio, la de la plantilla, o la de la subcategoría del producto vendido suelto); y nacen cuatro recursos de solo lectura para resolver esos ids: `product_categories`, `product_subcategories`, `service_categories` y `service_subcategories`. `products` no cambia.","Los eventos no cambian. Ningún otro recurso ni campo cambia."],"huella":"fb9f6eb4e72b006468e2054d526cddcccd34ff01a232291b40bb39a3c0cbf71d"},{"fecha":"2026-09-10","cambios":["LA API ES DEL PLAN EMPRESARIAL (Planes V1). Ninguna superficie cambia: los recursos, sus campos y los eventos son los mismos, y la huella de esta entrada es idéntica a la anterior. Lo que se estrena es una RESPUESTA: si la empresa baja de Empresarial, sus llaves `pfk_`, su llave DSS y sus webhooks siguen funcionando TREINTA DÍAS en solo lectura (la /v1 de la empresa nunca escribió); al vencer, las llaves se revocan y contestan `402 CAPABILITY_NOT_IN_PLAN` con `details.capacidad = \"api_y_webhooks\"`, `planMinimo = \"empresarial\"`, `desactivadaEl` y `revocadaEl`. Vuelve a Empresarial dentro de la ventana y todo sigue igual; después, la empresa emite llaves nuevas.","Crear o ampliar una integración (llaves, webhooks, buzón de pruebas, DSS) desde la gestión de la empresa exige Empresarial desde hoy; leer y revocar siguen abiertos en cualquier plan."],"huella":"01995fabe8b792c5a7ab30bac83a04d04ea8dbc7f53837060b105cd3959adb08"},{"fecha":"2026-09-08","cambios":["NACE `purchase_order_lines`: las LÍNEAS de la orden de compra (producto, cantidad pedida y recibida en milésimas, unidad de compra, factor congelado y costo unitario estimado SIN IVA). Cuelgan de `purchase_orders` por `purchaseOrderId` y no tienen sucursal propia: se consultan con `purchase_order={id}` o `product={id}`; el filtro `workshop` responde 400 FILTER_NOT_SUPPORTED en este recurso. Lo que de verdad ENTRÓ al almacén sigue en `inventory_movements` (una fila por producto recibido, con cantidad y costo real): la orden es la promesa; la recepción, el hecho.","LA CADENA DEL PROVEEDOR, completa y con filtro: `purchase_orders?supplier=…` (lo pedido), `purchase_order_lines?purchase_order=…` (qué se pidió), `inventory_receipts?purchase_order=…` o `?supplier=…` (lo que llegó, con el documento del proveedor y su IVA), `inventory_movements?receipt=…` (cada producto recibido, cantidad y costo), `expenses?receipt=…` (el gasto que esa recepción generó: importe TOTAL con IVA y el IVA contenido) y `expense_payments?expense=…` (los pagos al proveedor). Las devoluciones: `supplier_returns?receipt=…` o `?supplier=…`.","SIETE FILTROS DE RELACIÓN MÁS, con la misma regla que los cuatro de hoy en la mañana (cada recurso declara cuáles entiende; el que no, 400 con el nombre del filtro; todos entran en la firma del cursor): `appointment={id}` (en services, la orden que nació de esa cita), `supplier={id}`, `purchase_order={id}`, `receipt={id}`, `expense={id}`, `payment={id}` (en payment_applications y cash_movements, lo de ese cobro) y `product={id}` (existencias, movimientos, líneas de compra y renglones de orden donde se vendió). El OpenAPI de cada recurso lista los suyos.","DÓNDE ESTÁ LA BASE ANTES DE IMPUESTO, dicho de una vez porque cambia por documento: en `services` es `subtotalCents` (líneas aprobadas, sin IVA; `taxRate` es la tasa de ESA orden); en `service_items`, `unitPriceCents` y `totalCents` son sin IVA; en `fiscal_documents`, `grossSubtotalCents` es el SubTotal del CFDI antes de descuento y `taxTransferredCents`/`taxWithheldCents` los impuestos; en `inventory_receipts` y `expenses` el importe es el TOTAL con IVA y la base es `documentTotalCents − taxCents` / `amountCents − taxCents`; los pagos (`service_payments`, `expense_payments`) solo traen `amountCents`: el impuesto vive en el documento que pagan.","Los eventos no cambian. Los recursos y campos existentes tampoco: solo se suma `purchase_order_lines` y parámetros de consulta."],"huella":"01995fabe8b792c5a7ab30bac83a04d04ea8dbc7f53837060b105cd3959adb08"},{"fecha":"2026-09-08","cambios":["LAS LISTAS ESTRENAN FILTROS DE RELACIÓN: `service={id}`, `customer={id}`, `vehicle={id}` e `invoice={id}` acotan `GET /v1/{recurso}` a lo que cuelga de una orden, un cliente, un vehículo o un documento fiscal. Nacen de la pregunta más repetida de quien integra: «de este pago, ¿cuál factura?; de esta factura, ¿qué orden y qué renglones?; de esa orden, ¿qué cliente y qué cita?». Hasta hoy la única respuesta era bajar el recurso entero y cruzarlo de tu lado.","LA CADENA COMPLETA, por si la quieres de memoria: `service_payments?invoice=…` y `payment_applications?invoice=…` (los pagos que cubren una factura), `fiscal_documents?service=…` (las facturas de una orden), `service_items?service=…` (sus renglones: eso es lo que incluye la factura), `services?customer=…` / `?vehicle=…`, `appointments?service=…` (la cita que se convirtió en esa orden; si el cliente llegó sin agendar, no hay ninguna) y `remissions?invoice=…` (las remisiones que ampara).","CADA RECURSO DECLARA CUÁLES ENTIENDE y su OpenAPI los lista; pedir uno que no declara responde `400 FILTER_NOT_SUPPORTED` con el nombre del filtro, nunca se ignora en silencio (misma regla que `unit`/`group` en Fleet). Se combinan con `workshop` y `updated_since`, y entran en la firma del cursor: una página pedida con `service=` no se puede continuar sin él.","Los recursos, sus campos y los eventos NO cambian: solo se suman parámetros de consulta."],"huella":"328e0dea3d5ec29cf44f992fb738ce14561d5b3cf34050e23fa7de8226a667e7"},{"fecha":"2026-09-03","cambios":["EL VOCABULARIO DE EVENTOS PASA DE 16 A 46, y ninguno estrena recurso: todos laten por recursos que /v1 ya expone con sus campos, así que lo único que tienes que hacer para oírlos es suscribirte. La regla no cambia: un evento solo late si tu integración tiene marcado su recurso.","LA ORDEN, más allá de nacer y de moverse de columna: `service.updated` (cambiaron sus datos sin cambiar de etapa), `service.delivered` (el coche YA SALIÓ: `service.stage_changed` no sabía distinguir «terminado» de «entregado»), `service.hold_changed` (entró o salió de espera, con su motivo), `service.quote_updated` (cambió el total cotizado porque una línea nació, se editó o se borró), `service.quote_expired` (se acabó la vigencia sin decisión del cliente: `service.quote_decided` solo cubre aprobado y rechazado), `service.technician_assigned`, `service.rework_opened` (esta orden es un retrabajo, y su proyección dice de cuál nació), `service.aging_alert` (el barrido de antigüedad la marcó estancada) y la pareja `service.deleted` / `service.restored`.","`service.delivered` ENTREGA LA FILA DE LA ORDEN ENTREGADA, ni más ni menos: los campos de `services` que tengas marcados, igual que cualquier otro evento de la orden. NO viajan ahí quién recibió el coche ni el odómetro de salida —no son columnas de `services` y esta API no inventa campos para un webhook—; el kilómetro de salida se lee en `vehicles.mileage`, que la entrega deja al día cuando se capturó y es mayor al último conocido.","`service.deleted` ES LÁPIDA, con la misma regla que las del expediente: entrega `data: { id, deleted: true }` y ningún campo de la fila. `service.restored` no lo es — ahí la orden vuelve a estar viva y se proyecta entera.","LA CAJA Y EL EGRESO, para que la contabilidad deje de esperar al reporte manual: `cash_session.opened`, `cash_session.closed` (con su arqueo y su diferencia), `cash_movement.recorded` (el dinero que entra o sale del cajón FUERA de un cobro: retiro, depósito, reposición, fondo inicial y la devolución en efectivo de una nota de crédito, que hasta hoy no latía por ningún evento), `opening_receivable.collected` (el cobro de la cartera que migraste, que no cuelga de ninguna orden y por eso `payment.recorded` nunca lo vio), `expense.created`, `expense.cancelled`, `expense_payment.recorded` y `expense_payment.reversed`.","EL ALMACÉN: `inventory_receipt.created` y `.cancelled`, `purchase_order.created` y `.cancelled`, `supplier_return.created` y `.cancelled`, `inventory_adjustment.recorded` (la merma o la corrección, como fila del kardex con su valor), `inventory_count.applied` e `inventory.stock_low`.","`inventory.stock_low` LATE POR `inventory_stock`, no por `products`, y es a propósito: el hecho es la EXISTENCIA que quedó bajo mínimo, con su sucursal y su cantidad dentro; con el producto solo no podrías contestar «¿de qué sucursal?». Se deduplica como el aviso interno — mientras no se reponga no vuelve a cruzar—, así que no recibes el mismo grito dos veces.","EL PAPEL Y LA GENTE: `remission.cancelled` (el par que le faltaba a `remission.issued`, igual que `invoice.cancelled` del lado de la factura) y `employee.clocked_in` / `employee.clocked_out`. Los dos de asistencia laten por `employees` y entregan a la PERSONA en su vista mínima: el marcaje crudo —GPS, foto y notas— no es recurso de esta API y no va a serlo; el cuándo es el `createdAt` del cuerpo de la entrega.","HACIA EL TOKEN DE RED solo se suman los de la ORDEN que su panel ya ve (`network_services`): `service.updated`, `service.delivered`, `service.hold_changed`, `service.quote_updated` y `service.quote_expired`. La caja, el gasto, el almacén, la remisión, la asistencia, el retrabajo, la alerta de antigüedad y el técnico asignado no tienen recurso de red y para ella no existen.","EL EXPEDIENTE ESTRENA EVENTOS. `customers` y `vehicles` eran los dos recursos que esta API exponía sin un solo evento que oír: para mantener tu copia al día no te quedaba más que barrer `updated_since`. Nacen seis: `customer.created`, `customer.updated`, `customer.deleted`, `vehicle.created`, `vehicle.updated` y `vehicle.deleted`. Se suscriben como cualquier otro y solo laten si tu integración tiene marcado `customers` o `vehicles` según corresponda.","LAS BAJAS LLEGAN COMO LÁPIDA, dicho de frente porque no se parece a los demás eventos: `customer.deleted` y `vehicle.deleted` entregan `data: { id, deleted: true }` y ningún campo de la fila, ni siquiera los que tengas marcados. El borrado es lógico y esta API nunca sirve filas borradas —esa promesa no se rompe ni para un webhook—, así que lo que te mandamos es lo único que necesitas: cuál quitar de tu copia. El `id` es el mismo con el que te llegó al crearse y el cuándo es el `createdAt` del cuerpo. Si el registro se restaura antes de que salga la entrega, el aviso no se manda.","BORRAR UN CLIENTE ARRASTRA SUS VEHÍCULOS y aun así recibes UN SOLO `customer.deleted`, no un `vehicle.deleted` por coche: da por bajas sus unidades con ese latido. Es la diferencia que le importa a quien copia una flotilla de cien camiones.","Los eventos del expediente son de EMPRESA y no existen para el token de RED: el panel de red ve las órdenes de sus talleres vinculados, jamás el padrón de clientes de otra empresa.","Los recursos y sus campos NO cambian: lo único que se movió es el vocabulario de eventos."],"huella":"328e0dea3d5ec29cf44f992fb738ce14561d5b3cf34050e23fa7de8226a667e7"},{"fecha":"2026-09-02","cambios":["`vehicles` ESTRENA `colorKey`: la clave del catálogo de colores comunes en México (`blanco`, `negro`, `gris`, `plata`, `gris_oxford`, `rojo`, `azul`, `azul_marino`, `verde`, `amarillo`, `naranja`, `cafe`, `beige`, `arena`, `dorado`, `vino`, `morado`, `rosa`). Es el campo con el que por fin se puede AGRUPAR o COMPARAR por color: hasta hoy solo viajaba `color` en texto libre, y «Blanco», «blanco» y «BLANCO PERLA» eran tres colores distintos para cualquier integración.","`vehicles.color` NO cambia de nombre ni de forma, pero SÍ de garantía: cuando `colorKey` viene, `color` es la etiqueta canónica de esa clave —«Blanco», «Azul marino»—, sincronizada por el sistema. Cuando `colorKey` es `null` el vehículo tiene color libre («Otro») y `color` sigue siendo, palabra por palabra, lo que capturó el taller. Los vehículos que ya existían se reconciliaron contra el catálogo por texto: lo que casó ganó su clave y su etiqueta; lo que no casó se quedó intacto con `colorKey: null` — ningún color capturado se reinterpretó a la fuerza.","Los demás recursos, campos y eventos no cambian."],"huella":"0c7a571009fefc103aace504654ac20e4d0be20a039d5bf295ea5e062657ac08"},{"fecha":"2026-09-02","cambios":["CAMBIO DE CONTRATO (rompe compatibilidad) — remissions deja de exponer `issuerLogoUrl`. El campo prometía «el logo que se imprimió en el papel» y lo que viajaba era un identificador interno de archivo, sin ninguna forma de canjearlo por una imagen: servirlo era una promesa que el contrato no podía cumplir. Sale por el mismo criterio con el que `workshops` nunca expuso su `logoAssetId`. Si tu integración lo tenía marcado, vuelve a marcar los campos en la pantalla de Integraciones — `issuerLogoUrl` ya no es elegible. El logo sigue imprimiéndose en el PDF de la remisión, que es donde el papel lo lleva.","workshops.logoUrl SE DOCUMENTA COMO LEGADO, dicho de frente porque su significado cambió y el nombre no: es una columna heredada del stack anterior que ninguna pantalla puede fijar hoy. Desde esta fecha el logo del taller se configura como ARCHIVO —a nivel empresa, y lo heredan todas sus sucursales, o propio de una sucursal que lo sobreescribe—, y ninguno de esos dos viaja por este campo. Una sucursal puede devolver `logoUrl: null` y aun así imprimir su logo en la propuesta, en la remisión y en el reporte público. El campo NO se retira: los talleres migrados lo traen y quitarlo les rompería el membrete.","Los demás recursos, campos y eventos no cambian."],"huella":"11b2cef44886488c3d8ad82c6c73e6940d53f2fc9290e352a94b31dfed869df9"},{"fecha":"2026-08-29","cambios":["NACEN DOS RECURSOS con dato de TRABAJADORES, en solo lectura y con las mismas reglas de siempre (allowlist negada por omisión, filtro `workshop`, `updated_since`, cursor firmado y marca pii): `employees` —el padrón de personas en vista MÍNIMA: `id`, `roleId`, `status`, `workshopIds`, `createdAt`, `updatedAt`, más `name` y `email` marcados como dato personal— y `audit_log` —la bitácora: `occurredAt`, `workshopId`, `actorType`, `actorId` (pii), `action`, `resourceType` y `resourceId`—. Con `employees` los `…ByUserId` que esta API devuelve por todos lados por fin se resuelven a un nombre.","LO QUE ESOS DOS RECURSOS NO TRAEN, dicho de frente para que nadie lo espere: de `employees`, ni el hash de la contraseña ni su reloj (`passwordChangedAt`, intentos fallidos, bloqueos), ni sesiones, ni las preferencias de la persona (tema, zona horaria, idioma, avisos), ni sus assets de avatar y firma, ni la última conexión, ni `scope` —el alcance útil ya viaja como `workshopIds`—; de `audit_log`, el campo `metadata`, que es JSON libre donde cabe dato personal de un tercero. Ninguna de esas exclusiones es un descuido: son la autorización tal como se dio.","`employees.workshopIds` es un DERIVADO, no una columna: las sucursales se re-resuelven en cada consulta desde los accesos VIVOS de la persona, así que un acceso revocado desaparece de la lista al instante, y `workshop={id}` filtra el padrón por ese mismo acceso vivo (una persona no tiene columna de sucursal). Arreglo vacío = todavía no le han dado ninguna.","`audit_log` estrena un CURSOR NUEVO: ordena, pagina y responde `updated_since` por `occurredAt`. No tiene `updatedAt` y no le hace falta — sus filas se escriben una vez y jamás se editan ni se borran, así que paginar por el hecho no te hace perder ningún cambio."],"huella":"5b2d0fa43415f7b24c63b35b4ba4d00937aa897f7be6d40160f27ee81013e6dc"},{"fecha":"2026-08-26","cambios":["El esquema `error` se documenta como cualquier otro: `code`, `message` y `details` estrenan descripción (bilingüe, como toda la prosa del portal) y el esquema trae EJEMPLO de respuesta. Nada cambia en el contrato — mismos campos, mismos códigos —: era el único esquema publicado sin prosa, y la prueba de CI ahora barre `components.schemas` ENTERO y no solo los recursos, para que ningún esquema nuevo vuelva a nacer pelado.","DICHO DE FRENTE: los VALORES del enum `remissions.status` siguen en español (`emitida`, `amparada`, `cancelada`). El contrato inglés cubre NOMBRES —recursos, campos, filtros, códigos de error—; estos son datos, viven así en la base y renombrarlos rompería a quien ya compara contra ellos sin ganar nada. Están descritos en español y en inglés en la documentación; si algún día cambian, será con su entrada aquí y aviso previo."],"huella":"f5433fe054ca576c39fe404dfaa17abc2ea9908e562bea7aa0b6fefd5b4d5bc5"},{"fecha":"2026-08-26","cambios":["CAMBIO DE CONTRATO (rompe compatibilidad) — el ÚLTIMO campo en español de /v1 pasa a inglés. remissions · amparadaAt → coveredAt (cuándo quedó amparada por una factura). El dato es el mismo y la columna interna no cambió; solo el nombre del contrato. Si tu integración lo tenía marcado, vuelve a marcarlo en la pantalla de Integraciones: `amparadaAt` ya no es elegible.","LAS ÚNICAS PALABRAS QUE NO SE TRADUCEN, dichas de frente: `folio` (el consecutivo del papel mexicano, en services, inventory_receipts, inventory_counts, purchase_orders, supplier_returns, cash_sessions, fiscal_documents, remissions y network_services), `objectImpDr` (el ObjetoImp del complemento de documento relacionado, en payment_applications), `receiverCfdiUse`, `satCode`, `satPaymentForm`, `satProductKey`, `satRelationType` y `satUnitKey`. Son vocabulario del SAT: traducirlos te obligaría a destraducirlos para hablar con la autoridad. Todo lo demás del contrato es inglés, y hay prueba de CI que revisa palabra por palabra cada nombre de campo y truena con cualquier otra."],"huella":"f5433fe054ca576c39fe404dfaa17abc2ea9908e562bea7aa0b6fefd5b4d5bc5"},{"fecha":"2026-08-26","cambios":["CAMBIO DE CONTRATO (rompe compatibilidad) — los dos recursos de RED pasan a inglés. El contrato de /v1 es 100 % INGLÉS: nombres de recursos, de campos, de filtros y de códigos de error. Los recursos de red nacieron con los nombres del panel (una pantalla en español) y eran la única mezcla que quedaba. Se hace HOY, en pre-lanzamiento y SIN clientes productivos sobre /v1: no hay periodo de gracia porque no hay a quién romperle nada. Si tu integración es de las de prueba, vuelve a marcar los campos en la pantalla de Integraciones — los nombres viejos ya no son elegibles.","network_workshops · nombre → name · marca → brand · ciudad → city · estado → state · huso → timezone · moneda → currency · activa → active · empresa → organization (y dentro, empresa.nombre → organization.name) · vinculadaDesde → linkedSince.","network_services · creadaEn → createdAt · etapa → stage · etapaNombre → stageName · estadoDelPresupuesto → quoteStatus · decididoEn → decidedAt · aprobadoCentavos → approvedCents · facturadoCentavos → invoicedCents · vehiculo → vehicle (y dentro, vehiculo.marca → vehicle.make · vehiculo.modelo → vehicle.model · vehiculo.anio → vehicle.year).","NACEN 14 recursos de solo lectura, con las mismas reglas de siempre (allowlist negada por omisión, filtro `workshop`, `updated_since`, cursor firmado y marca pii): workshops (el padrón de sucursales, para resolver a nombre cualquier workshopId), service_stage_transitions (la bitácora de etapas del tablero), inventory_movements (el kardex), inventory_receipts, inventory_counts, purchase_orders, supplier_returns, cash_sessions (los cortes de caja), cash_movements (el libro de la caja), payment_applications (el complemento de pago desglosado), opening_receivables (la cartera importada del arranque), expense_payments (los pagos del gasto), fixed_assets y ledger_account_maps (el mapeo concepto→cuenta del contador).","Los libros APPEND-ONLY se amplían: además de service_payments, ahora también expense_payments, inventory_movements, cash_movements y service_stage_transitions ordenan, paginan y filtran `updated_since` por `createdAt`. Ninguno tiene `updatedAt` porque una fila suya no se edita: se contra-asienta con otra fila, que te llega como página nueva.","DOS RECURSOS QUE SE PIDIERON Y NO ESTÁN, dichos de frente: `journal_entries` (pólizas) no existe como tabla — la póliza se regenera de las filas del mes cerrado, así que exponerla sería inventar un contrato derivado que nadie ha decidido; y `monthly_closes` (cierres de mes) se queda fuera porque su fila muta al REABRIR un mes y no tiene sello que se mueva: `updated_since` no vería la reapertura y te dejaría con un mes cerrado que ya no lo está.","La DOCUMENTACIÓN deja de ser una lista de nombres: cada recurso y cada campo llevan descripción y tipo real, y cada recurso trae un EJEMPLO de respuesta con datos ficticios. La prosa es bilingüe: `GET /v1/openapi.json` en español, `GET /v1/openapi.json?lang=en` en inglés (el contrato sigue siendo inglés en los dos).","Una integración puede marcar hasta 40 recursos (antes 20): con el vocabulario nuevo, el tope viejo dejaba fuera una integración completa."],"huella":"a7df22c68a595f5dcdc8cf56984c4f9f9e8c7b354a664053e964f36c6ecec8a6"},{"fecha":"2026-08-26","cambios":["Nace POST /v1/webhooks/{id}/ping: con el token de la integración DUEÑA del webhook, entrega el evento ping FIRMADO (misma firma y mismo transporte que una entrega real, con doble firma si la rotación del secreto sigue viva) a la URL registrada y devuelve el resultado — { delivery: { id, event, status, createdAt }, result: { ok, httpStatus, durationMs, reason? } }. Es la ÚNICA ruta no-GET de /v1 y no escribe datos de nadie. Un webhook ajeno o inexistente responde el mismo 404; apagado, 409 WEBHOOK_DISABLED. Límite: 10 pings/min por token.","CORS: /v1 responde cabeceras CORS al origen del portal de desarrolladores de Pitflo (y solo a él), con preflight que anuncia Authorization, para que su playground consulte con tu token desde tu navegador. La regla del token no cambia: sigue siendo de servidor y tu propio sitio no puede llamar /v1 desde el navegador.","Los recursos, sus campos y el vocabulario de eventos NO cambian: la huella de la superficie es la misma que la del 2026-08-19."],"huella":"0c7acfc38b70fee71f9272a40663181100667d4d06b91519aab3df4364695567"},{"fecha":"2026-08-19","cambios":["Nace la documentación pública del API: este documento OpenAPI (GET /v1/openapi.json, sin token), el portal /developers y este changelog. El contrato /v1 no cambia: 13 recursos de empresa y 2 de red, solo lectura, con los campos elegibles y su marca pii tal como estaban.","Vocabulario de eventos de webhook documentado: service.created, service.stage_changed, service.quote_decided, payment.recorded, payment.reversed, invoice.stamped, invoice.cancelled, appointment.created, appointment.status_changed, remission.issued y ping (solo prueba)."],"huella":"0c7acfc38b70fee71f9272a40663181100667d4d06b91519aab3df4364695567"}],"x-lang":"es","x-fleet":{"recursos":["fleet_units","fleet_unit_specs","fleet_meter_readings","fleet_assignments","fleet_reservations","fleet_charges","fleet_fuel_logs","fleet_expenses","fleet_expense_schedules","fleet_fuel_exceptions","fleet_fuel_cards","fleet_fuel_transactions","fleet_documents","fleet_renewals","fleet_warranties","fleet_inspections","fleet_inspection_items","fleet_inspection_schedules","fleet_issues","fleet_faults","fleet_work_orders","fleet_work_order_lines","fleet_service_tasks","fleet_service_entries","fleet_service_entry_lines","fleet_maintenance_programs","fleet_unit_programs","fleet_reminder_cycles","fleet_parts","fleet_part_locations","fleet_inventory_stock","fleet_part_movements","fleet_part_receipts","fleet_part_transfers","fleet_part_counts","fleet_supplier_returns","fleet_warranty_claims","fleet_purchase_orders","fleet_purchase_order_lines","fleet_vendors","fleet_contacts","fleet_groups","fleet_unit_types","fleet_labels","fleet_custom_fields","fleet_workshop_links","fleet_comments","fleet_bulk_jobs","fleet_import_batches"],"eventos":[{"evento":"fleet.unit.created","recursosHabilitantes":["fleet_units"]},{"evento":"fleet.unit.updated","recursosHabilitantes":["fleet_units"]},{"evento":"fleet.unit.status_changed","recursosHabilitantes":["fleet_units"]},{"evento":"fleet.unit.deleted","recursosHabilitantes":["fleet_units"]},{"evento":"fleet.meter_reading.created","recursosHabilitantes":["fleet_meter_readings"]},{"evento":"fleet.meter_reading.updated","recursosHabilitantes":["fleet_meter_readings"]},{"evento":"fleet.meter_reading.deleted","recursosHabilitantes":["fleet_meter_readings"]},{"evento":"fleet.meter_reading.voided","recursosHabilitantes":["fleet_meter_readings"]},{"evento":"fleet.meter_reading.unvoided","recursosHabilitantes":["fleet_meter_readings"]},{"evento":"fleet.meter_reading.reset","recursosHabilitantes":["fleet_meter_readings"]},{"evento":"fleet.meter_reading.imported","recursosHabilitantes":["fleet_meter_readings"]},{"evento":"fleet.meter_reading.ingested","recursosHabilitantes":["fleet_meter_readings"]},{"evento":"fleet.fuel_log.created","recursosHabilitantes":["fleet_fuel_logs"]},{"evento":"fleet.fuel_log.updated","recursosHabilitantes":["fleet_fuel_logs"]},{"evento":"fleet.fuel_log.voided","recursosHabilitantes":["fleet_fuel_logs"]},{"evento":"fleet.fuel_log.duplicate_suspected","recursosHabilitantes":["fleet_fuel_logs"]},{"evento":"fleet.fuel_log.deleted","recursosHabilitantes":["fleet_fuel_logs"]},{"evento":"fleet.fuel_exception.opened","recursosHabilitantes":["fleet_fuel_exceptions"]},{"evento":"fleet.fuel_exception.confirmed","recursosHabilitantes":["fleet_fuel_exceptions"]},{"evento":"fleet.fuel_exception.justified","recursosHabilitantes":["fleet_fuel_exceptions"]},{"evento":"fleet.fuel_transaction.imported","recursosHabilitantes":["fleet_fuel_transactions"]},{"evento":"fleet.fuel_transaction.matched","recursosHabilitantes":["fleet_fuel_transactions"]},{"evento":"fleet.fuel_transaction.discarded","recursosHabilitantes":["fleet_fuel_transactions"]},{"evento":"fleet.expense.created","recursosHabilitantes":["fleet_expenses"]},{"evento":"fleet.expense.updated","recursosHabilitantes":["fleet_expenses"]},{"evento":"fleet.expense.cancelled","recursosHabilitantes":["fleet_expenses"]},{"evento":"fleet.expense.deleted","recursosHabilitantes":["fleet_expenses"]},{"evento":"fleet.expense_schedule.generated","recursosHabilitantes":["fleet_expense_schedules"]},{"evento":"fleet.expense_schedule.paused","recursosHabilitantes":["fleet_expense_schedules"]},{"evento":"fleet.expense_schedule.resumed","recursosHabilitantes":["fleet_expense_schedules"]},{"evento":"fleet.document.created","recursosHabilitantes":["fleet_documents"]},{"evento":"fleet.document.expiring","recursosHabilitantes":["fleet_documents"]},{"evento":"fleet.document.expired","recursosHabilitantes":["fleet_documents"]},{"evento":"fleet.document.auto_deleted","recursosHabilitantes":["fleet_documents"]},{"evento":"fleet.document.deleted","recursosHabilitantes":["fleet_documents"]},{"evento":"fleet.renewal.due","recursosHabilitantes":["fleet_renewals"]},{"evento":"fleet.renewal.overdue","recursosHabilitantes":["fleet_renewals"]},{"evento":"fleet.renewal.renewed","recursosHabilitantes":["fleet_renewals"]},{"evento":"fleet.warranty.expiring","recursosHabilitantes":["fleet_warranties"]},{"evento":"fleet.inspection.submitted","recursosHabilitantes":["fleet_inspections"]},{"evento":"fleet.inspection.item_failed","recursosHabilitantes":["fleet_inspections"]},{"evento":"fleet.inspection.overdue","recursosHabilitantes":["fleet_inspections","fleet_inspection_schedules"]},{"evento":"fleet.inspection.dvir_defect_uncertified","recursosHabilitantes":["fleet_inspections"]},{"evento":"fleet.inspection.item_out_of_range","recursosHabilitantes":["fleet_inspections","fleet_inspection_items"]},{"evento":"fleet.issue.created","recursosHabilitantes":["fleet_issues"]},{"evento":"fleet.issue.assigned","recursosHabilitantes":["fleet_issues"]},{"evento":"fleet.issue.status_changed","recursosHabilitantes":["fleet_issues"]},{"evento":"fleet.issue.resolved","recursosHabilitantes":["fleet_issues"]},{"evento":"fleet.issue.closed","recursosHabilitantes":["fleet_issues"]},{"evento":"fleet.issue.reopened","recursosHabilitantes":["fleet_issues"]},{"evento":"fleet.fault.created","recursosHabilitantes":["fleet_faults"]},{"evento":"fleet.fault.triaged","recursosHabilitantes":["fleet_faults"]},{"evento":"fleet.fault.resolved","recursosHabilitantes":["fleet_faults"]},{"evento":"fleet.work_order.created","recursosHabilitantes":["fleet_work_orders"]},{"evento":"fleet.work_order.status_changed","recursosHabilitantes":["fleet_work_orders"]},{"evento":"fleet.work_order.completed","recursosHabilitantes":["fleet_work_orders"]},{"evento":"fleet.service_entry.created","recursosHabilitantes":["fleet_service_entries"]},{"evento":"fleet.reminder.due_soon","recursosHabilitantes":["fleet_unit_programs"]},{"evento":"fleet.reminder.overdue","recursosHabilitantes":["fleet_unit_programs"]},{"evento":"fleet.reminder.completed","recursosHabilitantes":["fleet_unit_programs"]},{"evento":"fleet.reminder.snoozed","recursosHabilitantes":["fleet_unit_programs","fleet_reminder_cycles"]},{"evento":"fleet.reminder.next_due_overridden","recursosHabilitantes":["fleet_unit_programs","fleet_reminder_cycles"]},{"evento":"fleet.reminder.cycle_reopened","recursosHabilitantes":["fleet_unit_programs","fleet_reminder_cycles"]},{"evento":"fleet.program.assigned","recursosHabilitantes":["fleet_maintenance_programs"]},{"evento":"fleet.assignment.created","recursosHabilitantes":["fleet_assignments"]},{"evento":"fleet.assignment.closed","recursosHabilitantes":["fleet_assignments"]},{"evento":"fleet.assignment.cancelled","recursosHabilitantes":["fleet_assignments"]},{"evento":"fleet.assignment.updated","recursosHabilitantes":["fleet_assignments"]},{"evento":"fleet.assignment.deleted","recursosHabilitantes":["fleet_assignments"]},{"evento":"fleet.reservation.created","recursosHabilitantes":["fleet_reservations"]},{"evento":"fleet.reservation.approved","recursosHabilitantes":["fleet_reservations"]},{"evento":"fleet.reservation.denied","recursosHabilitantes":["fleet_reservations"]},{"evento":"fleet.reservation.cancelled","recursosHabilitantes":["fleet_reservations"]},{"evento":"fleet.reservation.checked_out","recursosHabilitantes":["fleet_reservations"]},{"evento":"fleet.reservation.checked_in","recursosHabilitantes":["fleet_reservations"]},{"evento":"fleet.reservation.extended","recursosHabilitantes":["fleet_reservations"]},{"evento":"fleet.reservation.returned_late","recursosHabilitantes":["fleet_reservations"]},{"evento":"fleet.reservation.conflicted","recursosHabilitantes":["fleet_reservations"]},{"evento":"fleet.reservation.no_show","recursosHabilitantes":["fleet_reservations"]},{"evento":"fleet.reservation.deleted","recursosHabilitantes":["fleet_reservations"]},{"evento":"fleet.charge.drafted","recursosHabilitantes":["fleet_charges"]},{"evento":"fleet.charge.issued","recursosHabilitantes":["fleet_charges"]},{"evento":"fleet.charge.cancelled","recursosHabilitantes":["fleet_charges"]},{"evento":"fleet.charge.adjusted","recursosHabilitantes":["fleet_charges"]},{"evento":"fleet.comment.created","recursosHabilitantes":["fleet_comments"]},{"evento":"fleet.mention.created","recursosHabilitantes":["fleet_comments"]},{"evento":"fleet.alert.triggered","recursosHabilitantes":["fleet_units"]},{"evento":"fleet.place.created","recursosHabilitantes":["fleet_places"]},{"evento":"fleet.place.updated","recursosHabilitantes":["fleet_places"]},{"evento":"fleet.place.archived","recursosHabilitantes":["fleet_places"]},{"evento":"fleet.obra.created","recursosHabilitantes":["fleet_jobs"]},{"evento":"fleet.obra.status_changed","recursosHabilitantes":["fleet_jobs"]},{"evento":"fleet.obra.unit_assigned","recursosHabilitantes":["fleet_jobs"]},{"evento":"fleet.obra.unit_unassigned","recursosHabilitantes":["fleet_jobs"]},{"evento":"fleet.obra.cost_reattributed","recursosHabilitantes":["fleet_jobs"]},{"evento":"fleet.geofence.entered","recursosHabilitantes":["fleet_places"]},{"evento":"fleet.geofence.exited","recursosHabilitantes":["fleet_places"]},{"evento":"fleet.geofence.dwell_exceeded","recursosHabilitantes":["fleet_places"]},{"evento":"fleet.purchase_order.created","recursosHabilitantes":["fleet_purchase_orders"]},{"evento":"fleet.purchase_order.approval_requested","recursosHabilitantes":["fleet_purchase_orders"]},{"evento":"fleet.purchase_order.approved","recursosHabilitantes":["fleet_purchase_orders"]},{"evento":"fleet.purchase_order.rejected","recursosHabilitantes":["fleet_purchase_orders"]},{"evento":"fleet.purchase_order.sent","recursosHabilitantes":["fleet_purchase_orders"]},{"evento":"fleet.purchase_order.partially_received","recursosHabilitantes":["fleet_purchase_orders"]},{"evento":"fleet.purchase_order.received","recursosHabilitantes":["fleet_purchase_orders"]},{"evento":"fleet.purchase_order.cancelled","recursosHabilitantes":["fleet_purchase_orders"]},{"evento":"fleet.receipt.created","recursosHabilitantes":["fleet_part_receipts"]},{"evento":"fleet.receipt.cancelled","recursosHabilitantes":["fleet_part_receipts"]},{"evento":"fleet.part.created","recursosHabilitantes":["fleet_parts"]},{"evento":"fleet.part.archived","recursosHabilitantes":["fleet_parts"]},{"evento":"fleet.part.low_stock","recursosHabilitantes":["fleet_parts","fleet_inventory_stock"]},{"evento":"fleet.part.out_of_stock","recursosHabilitantes":["fleet_parts","fleet_inventory_stock"]},{"evento":"fleet.part_movement.created","recursosHabilitantes":["fleet_part_movements"]},{"evento":"fleet.part_movement.cancelled","recursosHabilitantes":["fleet_part_movements"]},{"evento":"fleet.count.opened","recursosHabilitantes":["fleet_part_counts"]},{"evento":"fleet.count.applied","recursosHabilitantes":["fleet_part_counts"]},{"evento":"fleet.transfer.created","recursosHabilitantes":["fleet_part_transfers"]},{"evento":"fleet.transfer.received","recursosHabilitantes":["fleet_part_transfers"]},{"evento":"fleet.supplier_return.created","recursosHabilitantes":["fleet_supplier_returns"]},{"evento":"fleet.supplier_return.cancelled","recursosHabilitantes":["fleet_supplier_returns"]},{"evento":"fleet.warranty_claim.opened","recursosHabilitantes":["fleet_warranty_claims"]},{"evento":"fleet.warranty_claim.approved","recursosHabilitantes":["fleet_warranty_claims"]},{"evento":"fleet.warranty_claim.rejected","recursosHabilitantes":["fleet_warranty_claims"]},{"evento":"fleet.warranty_claim.credited","recursosHabilitantes":["fleet_warranty_claims"]},{"evento":"fleet.import_batch.applied","recursosHabilitantes":["fleet_import_batches"]},{"evento":"fleet.import_batch.reverted","recursosHabilitantes":["fleet_import_batches"]},{"evento":"fleet.vendor.created","recursosHabilitantes":["fleet_vendors"]},{"evento":"fleet.vendor.merged","recursosHabilitantes":["fleet_vendors"]},{"evento":"fleet.quote.received","recursosHabilitantes":["fleet_quotes"]},{"evento":"fleet.quote.decided","recursosHabilitantes":["fleet_quotes","fleet_approvals"]},{"evento":"fleet.quote.auto_approved","recursosHabilitantes":["fleet_quotes","fleet_approvals"]},{"evento":"fleet.recall.opened","recursosHabilitantes":["fleet_recalls"]},{"evento":"fleet.recall.acknowledged","recursosHabilitantes":["fleet_recalls"]},{"evento":"fleet.recall.resolved","recursosHabilitantes":["fleet_recalls"]},{"evento":"fleet.recall.dismissed","recursosHabilitantes":["fleet_recalls"]},{"evento":"fleet.recall.reopened","recursosHabilitantes":["fleet_recalls"]},{"evento":"fleet.assistance.requested","recursosHabilitantes":["fleet_assistance_requests"]},{"evento":"fleet.assistance.authorized","recursosHabilitantes":["fleet_assistance_requests"]},{"evento":"fleet.assistance.accepted","recursosHabilitantes":["fleet_assistance_requests"]},{"evento":"fleet.assistance.resolved","recursosHabilitantes":["fleet_assistance_requests"]},{"evento":"fleet.assistance.cancelled","recursosHabilitantes":["fleet_assistance_requests"]},{"evento":"fleet.workshop_link.status_changed","recursosHabilitantes":["fleet_workshop_links"]},{"evento":"fleet.shop_review.created","recursosHabilitantes":["fleet_shop_reviews"]},{"evento":"fleet.shop_review.replied","recursosHabilitantes":["fleet_shop_reviews"]},{"evento":"fleet.work_order.cancelled","recursosHabilitantes":["fleet_work_orders"]},{"evento":"fleet.work_order.reopened","recursosHabilitantes":["fleet_work_orders"]},{"evento":"fleet.work_order.signed","recursosHabilitantes":["fleet_work_orders"]},{"evento":"fleet.work_order.signature_removed","recursosHabilitantes":["fleet_work_orders"]},{"evento":"fleet.work_order.sent","recursosHabilitantes":["fleet_work_orders"]},{"evento":"fleet.service_entry.updated","recursosHabilitantes":["fleet_service_entries"]},{"evento":"fleet.unit.disposed","recursosHabilitantes":["fleet_units"]},{"evento":"fleet.unit.disposal_reverted","recursosHabilitantes":["fleet_units"]},{"evento":"fleet.unit.odometer_corrected","recursosHabilitantes":["fleet_units"]},{"evento":"fleet.unit.group_changed","recursosHabilitantes":["fleet_units"]},{"evento":"fleet.warranty.created","recursosHabilitantes":["fleet_warranties"]},{"evento":"fleet.warranty.updated","recursosHabilitantes":["fleet_warranties"]},{"evento":"fleet.inspection_schedule.created","recursosHabilitantes":["fleet_inspection_schedules"]},{"evento":"fleet.inspection_schedule.updated","recursosHabilitantes":["fleet_inspection_schedules"]},{"evento":"fleet.issue.recurrence_seen","recursosHabilitantes":["fleet_issues"]},{"evento":"fleet.bulk.completed","recursosHabilitantes":["fleet_bulk_jobs"]}],"bulk":{"ruta":"/v1/bulk/{entidad}","estado":"/v1/bulk/jobs/{jobId}","cancelar":"/v1/bulk/jobs/{jobId}/cancelar","operaciones":["crear","actualizar","upsert"],"entidades":["fleet_units","fleet_unit_specs","fleet_contacts","fleet_groups","fleet_unit_types","fleet_labels","fleet_meter_readings","fleet_assignments","fleet_fuel_logs","fleet_expenses","fleet_expense_schedules","fleet_vendors","fleet_parts","fleet_part_locations","fleet_part_movements","fleet_part_receipts","fleet_purchase_orders","fleet_issues","fleet_faults","fleet_work_orders","fleet_service_tasks","fleet_service_entries","fleet_maintenance_programs","fleet_unit_programs","fleet_reminder_cycles","fleet_inspection_schedules","fleet_renewals","fleet_warranties","fleet_places","fleet_jobs"],"capacidadRequerida":"bulk"},"oauth":{"autorizar":"/oauth/authorize","token":"/oauth/token","revocar":"/oauth/revoke","grants":"/oauth/grants","cuentas":"/v1/accounts","alcances":["leer:fleet_units","leer:fleet_unit_specs","leer:fleet_meter_readings","leer:fleet_assignments","leer:fleet_reservations","leer:fleet_charges","leer:fleet_fuel_logs","leer:fleet_expenses","leer:fleet_expense_schedules","leer:fleet_fuel_exceptions","leer:fleet_fuel_cards","leer:fleet_fuel_transactions","leer:fleet_documents","leer:fleet_renewals","leer:fleet_warranties","leer:fleet_inspections","leer:fleet_inspection_items","leer:fleet_inspection_schedules","leer:fleet_issues","leer:fleet_faults","leer:fleet_work_orders","leer:fleet_work_order_lines","leer:fleet_service_tasks","leer:fleet_service_entries","leer:fleet_service_entry_lines","leer:fleet_maintenance_programs","leer:fleet_unit_programs","leer:fleet_reminder_cycles","leer:fleet_parts","leer:fleet_part_locations","leer:fleet_inventory_stock","leer:fleet_part_movements","leer:fleet_part_receipts","leer:fleet_part_transfers","leer:fleet_part_counts","leer:fleet_supplier_returns","leer:fleet_warranty_claims","leer:fleet_purchase_orders","leer:fleet_purchase_order_lines","leer:fleet_vendors","leer:fleet_contacts","leer:fleet_groups","leer:fleet_unit_types","leer:fleet_labels","leer:fleet_custom_fields","leer:fleet_workshop_links","leer:fleet_comments","leer:fleet_bulk_jobs","leer:fleet_import_batches","escribir:fleet_units","escribir:fleet_unit_specs","escribir:fleet_contacts","escribir:fleet_groups","escribir:fleet_unit_types","escribir:fleet_labels","escribir:fleet_meter_readings","escribir:fleet_assignments","escribir:fleet_fuel_logs","escribir:fleet_expenses","escribir:fleet_expense_schedules","escribir:fleet_vendors","escribir:fleet_parts","escribir:fleet_part_locations","escribir:fleet_part_movements","escribir:fleet_part_receipts","escribir:fleet_purchase_orders","escribir:fleet_issues","escribir:fleet_faults","escribir:fleet_work_orders","escribir:fleet_service_tasks","escribir:fleet_service_entries","escribir:fleet_maintenance_programs","escribir:fleet_unit_programs","escribir:fleet_reminder_cycles","escribir:fleet_inspection_schedules","escribir:fleet_renewals","escribir:fleet_warranties","escribir:fleet_places","escribir:fleet_jobs","cuentas:leer","perfil:leer"]},"limitesPorTier":[{"clave":"unidadesActivas","porPlan":{"fleet_5":5,"fleet_50":50,"fleet_100":100,"fleet_ilimitado":null}},{"clave":"usuarios","porPlan":{"fleet_5":null,"fleet_50":null,"fleet_100":null,"fleet_ilimitado":null}},{"clave":"almacenamientoGiB","porPlan":{"fleet_5":null,"fleet_50":null,"fleet_100":null,"fleet_ilimitado":null}},{"clave":"llamadasApiDelMes","porPlan":{"fleet_5":null,"fleet_50":null,"fleet_100":null,"fleet_ilimitado":null}},{"clave":"apiReqPorMinuto","porPlan":{"fleet_5":null,"fleet_50":null,"fleet_100":null,"fleet_ilimitado":null}},{"clave":"webhooksActivos","porPlan":{"fleet_5":null,"fleet_50":null,"fleet_100":null,"fleet_ilimitado":null}},{"clave":"retencionDeDatos","porPlan":{"fleet_5":null,"fleet_50":null,"fleet_100":null,"fleet_ilimitado":null}},{"clave":"rolesPersonalizados","porPlan":{"fleet_5":null,"fleet_50":null,"fleet_100":null,"fleet_ilimitado":null}},{"clave":"recordSets","porPlan":{"fleet_5":null,"fleet_50":null,"fleet_100":null,"fleet_ilimitado":null}},{"clave":"automatizacionesActivas","porPlan":{"fleet_5":null,"fleet_50":null,"fleet_100":null,"fleet_ilimitado":null}},{"clave":"reportesGuardados","porPlan":{"fleet_5":null,"fleet_50":null,"fleet_100":null,"fleet_ilimitado":null}},{"clave":"reportesProgramados","porPlan":{"fleet_5":null,"fleet_50":null,"fleet_100":null,"fleet_ilimitado":null}},{"clave":"tableros","porPlan":{"fleet_5":null,"fleet_50":null,"fleet_100":null,"fleet_ilimitado":null}},{"clave":"integracionesActivas","porPlan":{"fleet_5":null,"fleet_50":null,"fleet_100":null,"fleet_ilimitado":null}},{"clave":"destinosDeDatos","porPlan":{"fleet_5":null,"fleet_50":null,"fleet_100":null,"fleet_ilimitado":null}},{"clave":"conectoresActivos","porPlan":{"fleet_5":null,"fleet_50":null,"fleet_100":null,"fleet_ilimitado":null}},{"clave":"bulkFilasPorJob","porPlan":{"fleet_5":null,"fleet_50":null,"fleet_100":null,"fleet_ilimitado":null}},{"clave":"bulkJobsConcurrentes","porPlan":{"fleet_5":null,"fleet_50":null,"fleet_100":null,"fleet_ilimitado":null}},{"clave":"automatizacionEjecucionesPorDia","porPlan":{"fleet_5":null,"fleet_50":null,"fleet_100":null,"fleet_ilimitado":null}}]}},"servers":[{"url":"/"}],"security":[{"token_pfk":[]}],"paths":{"/v1/workshops":{"get":{"operationId":"listar_workshops","summary":"Lista workshops (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/workshops"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/workshops/{id}":{"get":{"operationId":"ver_workshops","summary":"Un workshops por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/workshops"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/services":{"get":{"operationId":"listar_services","summary":"Lista services (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"service","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo que cuelga de esa orden (services.id): renglones, pagos, facturas, remisiones, transiciones, movimientos de inventario y las partidas de compra pedidas para ella (purchase_order_requests). En services es la orden misma; en appointments, la cita que se convirtió en esa orden."},{"name":"customer","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese cliente (customers.id): órdenes, vehículos, citas, facturas, remisiones y saldos iniciales."},{"name":"vehicle","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese vehículo (vehicles.id): órdenes y citas."},{"name":"appointment","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa cita (appointments.id): en services, la orden que nació de ella."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/services"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/services/{id}":{"get":{"operationId":"ver_services","summary":"Un services por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/services"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/service_items":{"get":{"operationId":"listar_service_items","summary":"Lista service_items (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"service","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo que cuelga de esa orden (services.id): renglones, pagos, facturas, remisiones, transiciones, movimientos de inventario y las partidas de compra pedidas para ella (purchase_order_requests). En services es la orden misma; en appointments, la cita que se convirtió en esa orden."},{"name":"product","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese producto (products.id): existencias, movimientos, líneas de compra y renglones de orden donde se vendió."},{"name":"category","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa categoría. En products es `product_categories.id`; en service_templates, service_subcategories y service_items es `service_categories.id` (en service_items, la categoría de VENTA sellada en el renglón); en product_subcategories, su categoría de producto; en las dos listas de categorías, la categoría misma."},{"name":"subcategory","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa subcategoría. En products es `product_subcategories.id`; en service_templates y service_items es `service_subcategories.id` (en service_items, la subcategoría de VENTA sellada); en las dos listas de subcategorías, la subcategoría misma."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/service_items"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/service_items/{id}":{"get":{"operationId":"ver_service_items","summary":"Un service_items por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/service_items"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/service_stage_transitions":{"get":{"operationId":"listar_service_stage_transitions","summary":"Lista service_stage_transitions (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"service","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo que cuelga de esa orden (services.id): renglones, pagos, facturas, remisiones, transiciones, movimientos de inventario y las partidas de compra pedidas para ella (purchase_order_requests). En services es la orden misma; en appointments, la cita que se convirtió en esa orden."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/service_stage_transitions"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/service_stage_transitions/{id}":{"get":{"operationId":"ver_service_stage_transitions","summary":"Un service_stage_transitions por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/service_stage_transitions"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/customers":{"get":{"operationId":"listar_customers","summary":"Lista customers (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"customer","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese cliente (customers.id): órdenes, vehículos, citas, facturas, remisiones y saldos iniciales."},{"name":"consent","in":"query","required":false,"schema":{"type":"string"},"description":"Solo los clientes con ese consentimiento OTORGADO y vigente (`consent=marketing`), o SIN él (`consent=marketing:false`: nunca contestó, negó, retiró, venció o el texto cambió desde que aceptó). La clave es la que la empresa escribió en su catálogo (`consent_types.key`); una que no existe es 400 `CONSENT_TYPE_NOT_FOUND`. En `vehicles` se resuelve contra el cliente dueño."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/customers"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/customers/{id}":{"get":{"operationId":"ver_customers","summary":"Un customers por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/customers"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/vehicles":{"get":{"operationId":"listar_vehicles","summary":"Lista vehicles (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"customer","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese cliente (customers.id): órdenes, vehículos, citas, facturas, remisiones y saldos iniciales."},{"name":"vehicle","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese vehículo (vehicles.id): órdenes y citas."},{"name":"consent","in":"query","required":false,"schema":{"type":"string"},"description":"Solo los clientes con ese consentimiento OTORGADO y vigente (`consent=marketing`), o SIN él (`consent=marketing:false`: nunca contestó, negó, retiró, venció o el texto cambió desde que aceptó). La clave es la que la empresa escribió en su catálogo (`consent_types.key`); una que no existe es 400 `CONSENT_TYPE_NOT_FOUND`. En `vehicles` se resuelve contra el cliente dueño."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/vehicles"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/vehicles/{id}":{"get":{"operationId":"ver_vehicles","summary":"Un vehicles por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/vehicles"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/consent_types":{"get":{"operationId":"listar_consent_types","summary":"Lista consent_types (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/consent_types"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/consent_types/{id}":{"get":{"operationId":"ver_consent_types","summary":"Un consent_types por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/consent_types"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/appointments":{"get":{"operationId":"listar_appointments","summary":"Lista appointments (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"service","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo que cuelga de esa orden (services.id): renglones, pagos, facturas, remisiones, transiciones, movimientos de inventario y las partidas de compra pedidas para ella (purchase_order_requests). En services es la orden misma; en appointments, la cita que se convirtió en esa orden."},{"name":"customer","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese cliente (customers.id): órdenes, vehículos, citas, facturas, remisiones y saldos iniciales."},{"name":"vehicle","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese vehículo (vehicles.id): órdenes y citas."},{"name":"appointment","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa cita (appointments.id): en services, la orden que nació de ella."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/appointments"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/appointments/{id}":{"get":{"operationId":"ver_appointments","summary":"Un appointments por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/appointments"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/products":{"get":{"operationId":"listar_products","summary":"Lista products (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"supplier","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese proveedor (suppliers.id): órdenes de compra, recepciones, devoluciones, gastos y productos."},{"name":"product","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese producto (products.id): existencias, movimientos, líneas de compra y renglones de orden donde se vendió."},{"name":"category","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa categoría. En products es `product_categories.id`; en service_templates, service_subcategories y service_items es `service_categories.id` (en service_items, la categoría de VENTA sellada en el renglón); en product_subcategories, su categoría de producto; en las dos listas de categorías, la categoría misma."},{"name":"subcategory","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa subcategoría. En products es `product_subcategories.id`; en service_templates y service_items es `service_subcategories.id` (en service_items, la subcategoría de VENTA sellada); en las dos listas de subcategorías, la subcategoría misma."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/products"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/products/{id}":{"get":{"operationId":"ver_products","summary":"Un products por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/products"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/product_categories":{"get":{"operationId":"listar_product_categories","summary":"Lista product_categories (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"category","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa categoría. En products es `product_categories.id`; en service_templates, service_subcategories y service_items es `service_categories.id` (en service_items, la categoría de VENTA sellada en el renglón); en product_subcategories, su categoría de producto; en las dos listas de categorías, la categoría misma."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/product_categories"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/product_categories/{id}":{"get":{"operationId":"ver_product_categories","summary":"Un product_categories por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/product_categories"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/product_subcategories":{"get":{"operationId":"listar_product_subcategories","summary":"Lista product_subcategories (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"category","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa categoría. En products es `product_categories.id`; en service_templates, service_subcategories y service_items es `service_categories.id` (en service_items, la categoría de VENTA sellada en el renglón); en product_subcategories, su categoría de producto; en las dos listas de categorías, la categoría misma."},{"name":"subcategory","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa subcategoría. En products es `product_subcategories.id`; en service_templates y service_items es `service_subcategories.id` (en service_items, la subcategoría de VENTA sellada); en las dos listas de subcategorías, la subcategoría misma."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/product_subcategories"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/product_subcategories/{id}":{"get":{"operationId":"ver_product_subcategories","summary":"Un product_subcategories por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/product_subcategories"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/inventory_stock":{"get":{"operationId":"listar_inventory_stock","summary":"Lista inventory_stock (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"product","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese producto (products.id): existencias, movimientos, líneas de compra y renglones de orden donde se vendió."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/inventory_stock"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/inventory_stock/{id}":{"get":{"operationId":"ver_inventory_stock","summary":"Un inventory_stock por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/inventory_stock"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/inventory_movements":{"get":{"operationId":"listar_inventory_movements","summary":"Lista inventory_movements (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"service","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo que cuelga de esa orden (services.id): renglones, pagos, facturas, remisiones, transiciones, movimientos de inventario y las partidas de compra pedidas para ella (purchase_order_requests). En services es la orden misma; en appointments, la cita que se convirtió en esa orden."},{"name":"receipt","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa recepción de compra (inventory_receipts.id): los movimientos de inventario que entró (una fila por producto, con cantidad y costo), el gasto que generó y las devoluciones que la reversan."},{"name":"product","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese producto (products.id): existencias, movimientos, líneas de compra y renglones de orden donde se vendió."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/inventory_movements"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/inventory_movements/{id}":{"get":{"operationId":"ver_inventory_movements","summary":"Un inventory_movements por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/inventory_movements"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/inventory_receipts":{"get":{"operationId":"listar_inventory_receipts","summary":"Lista inventory_receipts (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"supplier","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese proveedor (suppliers.id): órdenes de compra, recepciones, devoluciones, gastos y productos."},{"name":"purchase_order","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa orden de compra (purchase_orders.id): sus líneas y las recepciones que la surtieron."},{"name":"receipt","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa recepción de compra (inventory_receipts.id): los movimientos de inventario que entró (una fila por producto, con cantidad y costo), el gasto que generó y las devoluciones que la reversan."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/inventory_receipts"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/inventory_receipts/{id}":{"get":{"operationId":"ver_inventory_receipts","summary":"Un inventory_receipts por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/inventory_receipts"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/inventory_counts":{"get":{"operationId":"listar_inventory_counts","summary":"Lista inventory_counts (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/inventory_counts"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/inventory_counts/{id}":{"get":{"operationId":"ver_inventory_counts","summary":"Un inventory_counts por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/inventory_counts"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/purchase_orders":{"get":{"operationId":"listar_purchase_orders","summary":"Lista purchase_orders (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"supplier","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese proveedor (suppliers.id): órdenes de compra, recepciones, devoluciones, gastos y productos."},{"name":"purchase_order","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa orden de compra (purchase_orders.id): sus líneas y las recepciones que la surtieron."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/purchase_orders"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/purchase_orders/{id}":{"get":{"operationId":"ver_purchase_orders","summary":"Un purchase_orders por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/purchase_orders"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/purchase_order_lines":{"get":{"operationId":"listar_purchase_order_lines","summary":"Lista purchase_order_lines (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"purchase_order","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa orden de compra (purchase_orders.id): sus líneas y las recepciones que la surtieron."},{"name":"purchase_order_line","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa línea de orden de compra (purchase_order_lines.id): su reparto entre órdenes de servicio (purchase_order_requests). En purchase_order_lines es la línea misma."},{"name":"product","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese producto (products.id): existencias, movimientos, líneas de compra y renglones de orden donde se vendió."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/purchase_order_lines"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/purchase_order_lines/{id}":{"get":{"operationId":"ver_purchase_order_lines","summary":"Un purchase_order_lines por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/purchase_order_lines"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/purchase_order_requests":{"get":{"operationId":"listar_purchase_order_requests","summary":"Lista purchase_order_requests (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"service","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo que cuelga de esa orden (services.id): renglones, pagos, facturas, remisiones, transiciones, movimientos de inventario y las partidas de compra pedidas para ella (purchase_order_requests). En services es la orden misma; en appointments, la cita que se convirtió en esa orden."},{"name":"purchase_order_line","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa línea de orden de compra (purchase_order_lines.id): su reparto entre órdenes de servicio (purchase_order_requests). En purchase_order_lines es la línea misma."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/purchase_order_requests"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/purchase_order_requests/{id}":{"get":{"operationId":"ver_purchase_order_requests","summary":"Un purchase_order_requests por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/purchase_order_requests"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/supplier_returns":{"get":{"operationId":"listar_supplier_returns","summary":"Lista supplier_returns (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"supplier","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese proveedor (suppliers.id): órdenes de compra, recepciones, devoluciones, gastos y productos."},{"name":"receipt","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa recepción de compra (inventory_receipts.id): los movimientos de inventario que entró (una fila por producto, con cantidad y costo), el gasto que generó y las devoluciones que la reversan."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/supplier_returns"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/supplier_returns/{id}":{"get":{"operationId":"ver_supplier_returns","summary":"Un supplier_returns por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/supplier_returns"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/service_templates":{"get":{"operationId":"listar_service_templates","summary":"Lista service_templates (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"category","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa categoría. En products es `product_categories.id`; en service_templates, service_subcategories y service_items es `service_categories.id` (en service_items, la categoría de VENTA sellada en el renglón); en product_subcategories, su categoría de producto; en las dos listas de categorías, la categoría misma."},{"name":"subcategory","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa subcategoría. En products es `product_subcategories.id`; en service_templates y service_items es `service_subcategories.id` (en service_items, la subcategoría de VENTA sellada); en las dos listas de subcategorías, la subcategoría misma."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/service_templates"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/service_templates/{id}":{"get":{"operationId":"ver_service_templates","summary":"Un service_templates por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/service_templates"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/service_categories":{"get":{"operationId":"listar_service_categories","summary":"Lista service_categories (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"category","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa categoría. En products es `product_categories.id`; en service_templates, service_subcategories y service_items es `service_categories.id` (en service_items, la categoría de VENTA sellada en el renglón); en product_subcategories, su categoría de producto; en las dos listas de categorías, la categoría misma."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/service_categories"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/service_categories/{id}":{"get":{"operationId":"ver_service_categories","summary":"Un service_categories por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/service_categories"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/service_subcategories":{"get":{"operationId":"listar_service_subcategories","summary":"Lista service_subcategories (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"category","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa categoría. En products es `product_categories.id`; en service_templates, service_subcategories y service_items es `service_categories.id` (en service_items, la categoría de VENTA sellada en el renglón); en product_subcategories, su categoría de producto; en las dos listas de categorías, la categoría misma."},{"name":"subcategory","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa subcategoría. En products es `product_subcategories.id`; en service_templates y service_items es `service_subcategories.id` (en service_items, la subcategoría de VENTA sellada); en las dos listas de subcategorías, la subcategoría misma."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/service_subcategories"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/service_subcategories/{id}":{"get":{"operationId":"ver_service_subcategories","summary":"Un service_subcategories por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/service_subcategories"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/suppliers":{"get":{"operationId":"listar_suppliers","summary":"Lista suppliers (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"supplier","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese proveedor (suppliers.id): órdenes de compra, recepciones, devoluciones, gastos y productos."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/suppliers"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/suppliers/{id}":{"get":{"operationId":"ver_suppliers","summary":"Un suppliers por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/suppliers"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/service_payments":{"get":{"operationId":"listar_service_payments","summary":"Lista service_payments (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"service","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo que cuelga de esa orden (services.id): renglones, pagos, facturas, remisiones, transiciones, movimientos de inventario y las partidas de compra pedidas para ella (purchase_order_requests). En services es la orden misma; en appointments, la cita que se convirtió en esa orden."},{"name":"invoice","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo ligado a ese documento fiscal (fiscal_documents.id): los pagos que la cubren, sus aplicaciones y las remisiones que ampara. Un recurso que no lo declara responde 400 FILTER_NOT_SUPPORTED."},{"name":"payment","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese cobro (service_payments.id): sus aplicaciones a facturas y su movimiento de caja."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/service_payments"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/service_payments/{id}":{"get":{"operationId":"ver_service_payments","summary":"Un service_payments por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/service_payments"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/payment_applications":{"get":{"operationId":"listar_payment_applications","summary":"Lista payment_applications (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"service","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo que cuelga de esa orden (services.id): renglones, pagos, facturas, remisiones, transiciones, movimientos de inventario y las partidas de compra pedidas para ella (purchase_order_requests). En services es la orden misma; en appointments, la cita que se convirtió en esa orden."},{"name":"invoice","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo ligado a ese documento fiscal (fiscal_documents.id): los pagos que la cubren, sus aplicaciones y las remisiones que ampara. Un recurso que no lo declara responde 400 FILTER_NOT_SUPPORTED."},{"name":"payment","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese cobro (service_payments.id): sus aplicaciones a facturas y su movimiento de caja."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/payment_applications"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/payment_applications/{id}":{"get":{"operationId":"ver_payment_applications","summary":"Un payment_applications por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/payment_applications"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/opening_receivables":{"get":{"operationId":"listar_opening_receivables","summary":"Lista opening_receivables (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"customer","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese cliente (customers.id): órdenes, vehículos, citas, facturas, remisiones y saldos iniciales."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/opening_receivables"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/opening_receivables/{id}":{"get":{"operationId":"ver_opening_receivables","summary":"Un opening_receivables por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/opening_receivables"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/cash_sessions":{"get":{"operationId":"listar_cash_sessions","summary":"Lista cash_sessions (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/cash_sessions"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/cash_sessions/{id}":{"get":{"operationId":"ver_cash_sessions","summary":"Un cash_sessions por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cash_sessions"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/cash_movements":{"get":{"operationId":"listar_cash_movements","summary":"Lista cash_movements (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"invoice","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo ligado a ese documento fiscal (fiscal_documents.id): los pagos que la cubren, sus aplicaciones y las remisiones que ampara. Un recurso que no lo declara responde 400 FILTER_NOT_SUPPORTED."},{"name":"payment","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese cobro (service_payments.id): sus aplicaciones a facturas y su movimiento de caja."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/cash_movements"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/cash_movements/{id}":{"get":{"operationId":"ver_cash_movements","summary":"Un cash_movements por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cash_movements"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/fiscal_documents":{"get":{"operationId":"listar_fiscal_documents","summary":"Lista fiscal_documents (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"service","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo que cuelga de esa orden (services.id): renglones, pagos, facturas, remisiones, transiciones, movimientos de inventario y las partidas de compra pedidas para ella (purchase_order_requests). En services es la orden misma; en appointments, la cita que se convirtió en esa orden."},{"name":"customer","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese cliente (customers.id): órdenes, vehículos, citas, facturas, remisiones y saldos iniciales."},{"name":"invoice","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo ligado a ese documento fiscal (fiscal_documents.id): los pagos que la cubren, sus aplicaciones y las remisiones que ampara. Un recurso que no lo declara responde 400 FILTER_NOT_SUPPORTED."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/fiscal_documents"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/fiscal_documents/{id}":{"get":{"operationId":"ver_fiscal_documents","summary":"Un fiscal_documents por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/fiscal_documents"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/expenses":{"get":{"operationId":"listar_expenses","summary":"Lista expenses (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"supplier","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese proveedor (suppliers.id): órdenes de compra, recepciones, devoluciones, gastos y productos."},{"name":"receipt","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de esa recepción de compra (inventory_receipts.id): los movimientos de inventario que entró (una fila por producto, con cantidad y costo), el gasto que generó y las devoluciones que la reversan."},{"name":"expense","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese gasto (expenses.id): los pagos al proveedor que lo liquidan."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/expenses"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/expenses/{id}":{"get":{"operationId":"ver_expenses","summary":"Un expenses por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/expenses"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/expense_payments":{"get":{"operationId":"listar_expense_payments","summary":"Lista expense_payments (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"expense","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese gasto (expenses.id): los pagos al proveedor que lo liquidan."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/expense_payments"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/expense_payments/{id}":{"get":{"operationId":"ver_expense_payments","summary":"Un expense_payments por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/expense_payments"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/remissions":{"get":{"operationId":"listar_remissions","summary":"Lista remissions (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"service","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo que cuelga de esa orden (services.id): renglones, pagos, facturas, remisiones, transiciones, movimientos de inventario y las partidas de compra pedidas para ella (purchase_order_requests). En services es la orden misma; en appointments, la cita que se convirtió en esa orden."},{"name":"customer","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo de ese cliente (customers.id): órdenes, vehículos, citas, facturas, remisiones y saldos iniciales."},{"name":"invoice","in":"query","required":false,"schema":{"type":"string"},"description":"Solo lo ligado a ese documento fiscal (fiscal_documents.id): los pagos que la cubren, sus aplicaciones y las remisiones que ampara. Un recurso que no lo declara responde 400 FILTER_NOT_SUPPORTED."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/remissions"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/remissions/{id}":{"get":{"operationId":"ver_remissions","summary":"Un remissions por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/remissions"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/fixed_assets":{"get":{"operationId":"listar_fixed_assets","summary":"Lista fixed_assets (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/fixed_assets"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/fixed_assets/{id}":{"get":{"operationId":"ver_fixed_assets","summary":"Un fixed_assets por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/fixed_assets"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/ledger_account_maps":{"get":{"operationId":"listar_ledger_account_maps","summary":"Lista ledger_account_maps (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ledger_account_maps"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/ledger_account_maps/{id}":{"get":{"operationId":"ver_ledger_account_maps","summary":"Un ledger_account_maps por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ledger_account_maps"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/employees":{"get":{"operationId":"listar_employees","summary":"Lista employees (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/employees"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/employees/{id}":{"get":{"operationId":"ver_employees","summary":"Un employees por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/employees"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/audit_log":{"get":{"operationId":"listar_audit_log","summary":"Lista audit_log (orden estable, cursor firmado)","description":"Recurso de EMPRESA (token de empresa). Los borrados lógicos no salen.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/audit_log"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/audit_log/{id}":{"get":{"operationId":"ver_audit_log","summary":"Un audit_log por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/audit_log"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/network_workshops":{"get":{"operationId":"listar_network_workshops","summary":"Lista network_workshops (orden estable, cursor firmado)","description":"Recurso de RED (token de red): re-resuelve vínculos vivos y permisos de detalle vigentes en cada consulta.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/network_workshops"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/network_workshops/{id}":{"get":{"operationId":"ver_network_workshops","summary":"Un network_workshops por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/network_workshops"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/network_services":{"get":{"operationId":"listar_network_services","summary":"Lista network_services (orden estable, cursor firmado)","description":"Recurso de RED (token de red): re-resuelve vínculos vivos y permisos de detalle vigentes en cada consulta.","security":[{"token_pfk":[]}],"parameters":[{"name":"workshop","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra la sucursal en recursos operativos; se ignora en el expediente de empresa (customers, vehicles); en catálogos con fila corporativa (products, product_categories, product_subcategories, suppliers, service_templates, service_categories, expenses, fixed_assets, ledger_account_maps) resuelve la sucursal MÁS el catálogo corporativo; en `workshops` filtra por el id de la sucursal misma. `service_subcategories` no lo entiende (400 FILTER_NOT_SUPPORTED): el alcance vive en su categoría."},{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Solo filas actualizadas desde esta fecha (gte). En los libros append-only filtra por `createdAt`."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"El cursor opaco de la página anterior. De otro token, otro recurso u otros filtros: 400 INVALID_CURSOR."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Tamaño de página; 50 por omisión, 100 como tope."}],"responses":{"200":{"description":"Página con SOLO los campos marcados en tu integración.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/network_services"}},"cursor":{"type":["string","null"],"description":"null = no hay más páginas."}},"required":["data","cursor"]}}}},"400":{"$ref":"#/components/responses/error_400"},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/network_services/{id}":{"get":{"operationId":"ver_network_services","summary":"Un network_services por id","description":"El id ajeno y el id inexistente responden EL MISMO 404: fuera de alcance no existe.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del recurso (ULID). Un id de otra empresa responde el mismo 404 que uno inexistente."}],"responses":{"200":{"description":"El objeto, solo con los campos marcados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/network_services"}}}},"401":{"$ref":"#/components/responses/error_401"},"402":{"$ref":"#/components/responses/error_402"},"403":{"$ref":"#/components/responses/error_403"},"404":{"$ref":"#/components/responses/error_404"},"429":{"$ref":"#/components/responses/error_429"}}}},"/v1/webhooks/{id}/ping":{"post":{"operationId":"ping_webhook","summary":"Manda un ping firmado a tu webhook y devuelve el resultado","description":"Entrega SÍNCRONA: el cuerpo y el header `Pitflo-Firma` son EXACTAMENTE los de una entrega real (incluida la doble firma durante la ventana de rotación del secreto), así que si tu verificador acepta este ping, aceptará tus eventos.\nEl webhook debe ser de la integración de TU token: cualquier otro id responde 404. Un webhook apagado responde 409.\nLímite: 10 pings/min por token (cada ping es un POST a un tercero). Queda en tu bitácora de entregas como un `ping` con su desenlace, sin tocar la racha de fallos del webhook.","security":[{"token_pfk":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"El id del webhook (lo da tu pantalla de Integraciones)."}],"responses":{"200":{"description":"El ping salió (haya contestado bien o mal tu URL): `result.ok` dice si tu servidor respondió 2xx.","content":{"application/json":{"schema":{"type":"object","properties":{"delivery":{"type":"object","properties":{"id":{"type":"string","description":"El mismo `id` que viaja firmado en el cuerpo entregado."},"event":{"type":"string","const":"ping"},"status":{"type":"string","enum":["delivered","dead"]},"createdAt":{"type":"string","format":"date-time"}},"required":["id","event","status","createdAt"]},"result":{"type":"object","properties":{"ok":{"type":"boolean","description":"true solo con 2xx (un 3xx es fallo)."},"httpStatus":{"type":["integer","null"],"description":"Status de tu servidor, o null si no hubo respuesta (timeout, DNS, IP vetada)."},"durationMs":{"type":"integer","description":"Ida y vuelta medida alrededor del POST."},"reason":{"type":"string","description":"Por qué no salió, cuando ni siquiera se conectó."}},"required":["ok","httpStatus","durationMs"]}},"required":["delivery","result"]}}}},"401":{"$ref":"#/components/responses/error_401"},"404":{"$ref":"#/components/responses/error_404"},"409":{"$ref":"#/components/responses/error_409"},"429":{"$ref":"#/components/responses/error_429"}}}}},"webhooks":{"service.created":{"post":{"summary":"Entrega firmada del evento service.created.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"service.created"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"service.stage_changed":{"post":{"summary":"Entrega firmada del evento service.stage_changed.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"service.stage_changed"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"service.quote_decided":{"post":{"summary":"Entrega firmada del evento service.quote_decided.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"service.quote_decided"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"payment.recorded":{"post":{"summary":"Entrega firmada del evento payment.recorded.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"payment.recorded"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"payment.reversed":{"post":{"summary":"Entrega firmada del evento payment.reversed.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"payment.reversed"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"invoice.stamped":{"post":{"summary":"Entrega firmada del evento invoice.stamped.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"invoice.stamped"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"invoice.cancelled":{"post":{"summary":"Entrega firmada del evento invoice.cancelled.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"invoice.cancelled"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"appointment.created":{"post":{"summary":"Entrega firmada del evento appointment.created.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"appointment.created"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"appointment.status_changed":{"post":{"summary":"Entrega firmada del evento appointment.status_changed.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"appointment.status_changed"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"remission.issued":{"post":{"summary":"Entrega firmada del evento remission.issued.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"remission.issued"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"customer.created":{"post":{"summary":"Se registra un cliente.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"customer.created"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"customer.updated":{"post":{"summary":"Se actualiza un cliente.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"customer.updated"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"customer.deleted":{"post":{"summary":"Se da de baja un cliente.","description":"Este evento NO entrega la fila: entrega su lápida. Úsalo para quitar ese `id` de tu copia. Si restauran el registro antes de que salga la entrega, el aviso no se manda.\n\nCada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"customer.deleted"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"La LÁPIDA de la baja: `{ id, deleted: true }`, y nada más. No lleva campos de la fila —ni los que tengas marcados— porque la fila ya no se sirve por esta API; el `id` es el mismo con el que te llegó al crearse, y el cuándo es el `createdAt` de este cuerpo."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"vehicle.created":{"post":{"summary":"Se registra un vehículo.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"vehicle.created"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"vehicle.updated":{"post":{"summary":"Se actualiza un vehículo.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"vehicle.updated"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"vehicle.deleted":{"post":{"summary":"Se da de baja un vehículo.","description":"Este evento NO entrega la fila: entrega su lápida. Úsalo para quitar ese `id` de tu copia. Si restauran el registro antes de que salga la entrega, el aviso no se manda.\n\nCada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"vehicle.deleted"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"La LÁPIDA de la baja: `{ id, deleted: true }`, y nada más. No lleva campos de la fila —ni los que tengas marcados— porque la fila ya no se sirve por esta API; el `id` es el mismo con el que te llegó al crearse, y el cuándo es el `createdAt` de este cuerpo."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"customer.consent_changed":{"post":{"summary":"Un cliente otorga, niega o retira un consentimiento.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"customer.consent_changed"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"La CARGA del hecho: `customerId`, `key` (la clave del tipo), `decision` (`otorgado`, `negado` o `retirado`), `version` (la del texto que el cliente decidió), `decidedAt` y `source` (`reporte`, `cita` o `mostrador`), más `customer`: la fila del cliente proyectada con los campos que marcaste (su `consents` trae el estado completo si lo marcaste)."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"service.updated":{"post":{"summary":"Se actualiza una orden.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"service.updated"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"service.deleted":{"post":{"summary":"Se da de baja una orden.","description":"Este evento NO entrega la fila: entrega su lápida. Úsalo para quitar ese `id` de tu copia. Si restauran el registro antes de que salga la entrega, el aviso no se manda.\n\nCada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"service.deleted"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"La LÁPIDA de la baja: `{ id, deleted: true }`, y nada más. No lleva campos de la fila —ni los que tengas marcados— porque la fila ya no se sirve por esta API; el `id` es el mismo con el que te llegó al crearse, y el cuándo es el `createdAt` de este cuerpo."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"service.restored":{"post":{"summary":"Se restaura una orden.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"service.restored"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"service.delivered":{"post":{"summary":"Se entrega el vehículo.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"service.delivered"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"service.hold_changed":{"post":{"summary":"Una orden se detiene o se reanuda.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"service.hold_changed"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"service.quote_updated":{"post":{"summary":"Cambia el presupuesto.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"service.quote_updated"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"service.quote_expired":{"post":{"summary":"Vence un presupuesto.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"service.quote_expired"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"service.technician_assigned":{"post":{"summary":"Cambia el técnico de una orden.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"service.technician_assigned"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"service.rework_opened":{"post":{"summary":"Se abre un retrabajo.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"service.rework_opened"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"service.aging_alert":{"post":{"summary":"Una orden se estanca.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"service.aging_alert"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"cash_session.opened":{"post":{"summary":"Se abre una sesión de caja.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"cash_session.opened"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"cash_session.closed":{"post":{"summary":"Se cierra una sesión de caja.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"cash_session.closed"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"cash_movement.recorded":{"post":{"summary":"Se mueve efectivo en caja.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"cash_movement.recorded"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"opening_receivable.collected":{"post":{"summary":"Se cobra un saldo inicial.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"opening_receivable.collected"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"expense.created":{"post":{"summary":"Se registra un gasto.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"expense.created"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"expense.cancelled":{"post":{"summary":"Se cancela un gasto.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"expense.cancelled"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"expense_payment.recorded":{"post":{"summary":"Se paga un gasto.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"expense_payment.recorded"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"expense_payment.reversed":{"post":{"summary":"Se reversa el pago de un gasto.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"expense_payment.reversed"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"inventory_receipt.created":{"post":{"summary":"Entra mercancía al almacén.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"inventory_receipt.created"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"inventory_receipt.cancelled":{"post":{"summary":"Se cancela una recepción.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"inventory_receipt.cancelled"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"purchase_order.created":{"post":{"summary":"Se crea una orden de compra.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"purchase_order.created"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"purchase_order.cancelled":{"post":{"summary":"Se cancela una orden de compra.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"purchase_order.cancelled"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"supplier_return.created":{"post":{"summary":"Se devuelve mercancía al proveedor.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"supplier_return.created"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"supplier_return.cancelled":{"post":{"summary":"Se cancela una devolución al proveedor.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"supplier_return.cancelled"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"inventory_adjustment.recorded":{"post":{"summary":"Se ajusta el inventario.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"inventory_adjustment.recorded"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"inventory_count.applied":{"post":{"summary":"Se aplica un conteo físico.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"inventory_count.applied"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"inventory.stock_low":{"post":{"summary":"Una existencia queda bajo su mínimo.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"inventory.stock_low"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"remission.cancelled":{"post":{"summary":"Se cancela una remisión.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"remission.cancelled"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"employee.clocked_in":{"post":{"summary":"Alguien registra su entrada.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"employee.clocked_in"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"employee.clocked_out":{"post":{"summary":"Alguien registra su salida.","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"employee.clocked_out"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}},"ping":{"post":{"summary":"Entrega de prueba del botón «Mandar prueba».","description":"Cada entrega va firmada con el header `Pitflo-Firma: t=<epoch>,v1=<hex hmac_sha256(secret, \"<t>.<cuerpo>\")>` (el secreto `pfw_` se enseña una vez al crear el webhook).\nVerifícala así: rechaza si `|ahora − t| > 5 min`, compara el HMAC en tiempo constante y deduplica por `id` (estable entre los 6 intentos).\nEntrega: POST a tu URL `https` puerto 443, timeout 10 s; 2xx = entregada (un 3xx cuenta como fallo). Reintentos 1 m → 5 m → 30 m → 2 h → 8 h; al 6.º fallo la entrega muere; 10 muertas seguidas apagan el webhook con correo al gestor. Retención de entregas: 30 días (solo metadatos).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Estable entre reintentos: tu llave de idempotencia."},"event":{"type":"string","const":"ping"},"createdAt":{"type":"string","format":"date-time"},"version":{"type":"string","const":"v1"},"data":{"type":"object","description":"El recurso proyectado contra los campos marcados VIGENTES al entregar."}},"required":["id","event","createdAt","version","data"]}}}},"responses":{"200":{"description":"Cualquier 2xx cuenta como entregada (un 3xx no)."}}}}},"components":{"securitySchemes":{"token_pfk":{"type":"http","scheme":"bearer","bearerFormat":"pfk_…","description":"Token de integración (se emite en Integraciones y se enseña UNA vez). De SERVIDOR: no lo publiques en un navegador ni en una app tuya — la única excepción es el playground del portal de Pitflo, donde TÚ pegas tu propio token y vive solo en la pestaña. Viaja SOLO por header."}},"schemas":{"error":{"type":"object","description":"Formato canónico de error. Códigos de /v1: API_KEY_REQUIRED, API_KEY_INVALID, API_KEY_REVOKED, API_KEY_EXPIRED (401) · RESOURCE_NOT_ENABLED (403: el recurso no está habilitado en TU integración) · INVALID_CURSOR (400) · NOT_FOUND (404) · 429 con Retry-After.","properties":{"code":{"type":"string","description":"El código estable del error, en MAYÚSCULAS con guion bajo (p. ej. `API_KEY_INVALID`). Es lo único a lo que debes programar: el texto puede cambiar, el código no."},"message":{"type":"string","description":"La explicación legible del error, para tus registros y tu soporte. NO la parsees: cambia de redacción y de idioma."},"details":{"type":"object","description":"Detalle opcional del error, como objeto: presente solo cuando aporta algo (p. ej. el recurso no habilitado). Puede faltar."}},"required":["code","message"],"example":{"code":"RESOURCE_NOT_ENABLED","message":"El recurso `remissions` no está habilitado en tu integración.","details":{"resource":"remissions"}}},"workshops":{"type":"object","description":"El padrón de SUCURSALES de tu empresa: el diccionario con el que resuelves a nombre cualquier `workshopId` que devuelva esta API. Incluye la identidad fiscal con la que cada sucursal factura y sus series de folios.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"name":{"type":"string","description":"Nombre comercial de la sucursal, tal como lo ve el mostrador."},"slug":{"type":"string","description":"Identificador legible y estable de la sucursal dentro de la empresa (minúsculas y guiones)."},"code":{"type":"string","description":"Clave corta de la sucursal para reportes y etiquetas."},"brand":{"type":"string","description":"Marca o enseña bajo la que opera la sucursal; nula si opera con la de la empresa."},"status":{"type":"string","description":"Estado de la sucursal: `active` o `inactive`. Las sucursales dadas de baja no salen por esta API."},"timezone":{"type":"string","description":"Zona horaria IANA de la sucursal (p. ej. `America/Monterrey`). Toda fecha de esta API viaja en UTC: esta es la zona con la que se lee."},"currency":{"type":"string","description":"Moneda de la sucursal en ISO 4217 (`MXN`). Todo importe en centavos de esta moneda."},"legalName":{"type":"string","description":"Razón social con la que la sucursal emite comprobantes."},"taxId":{"type":"string","description":"RFC emisor de la sucursal."},"taxRegime":{"type":"string","description":"Régimen fiscal del SAT del emisor (clave de tres dígitos)."},"addressLine":{"type":"string","description":"Calle y número del domicilio de la sucursal."},"addressNeighborhood":{"type":"string","description":"Colonia."},"addressCity":{"type":"string","description":"Municipio o ciudad."},"addressState":{"type":"string","description":"Estado de la República."},"addressPostalCode":{"type":"string","description":"Código postal; es el que viaja como lugar de expedición en el CFDI."},"logoUrl":{"type":"string","description":"LEGADO. Columna heredada del stack anterior: una URL libre que ninguna pantalla puede fijar hoy. **No es «el logo de la sucursal»**: desde 2026-09-02 el logo se configura como archivo a nivel empresa (se hereda a todas sus sucursales) o propio de la sucursal, y ninguno de esos dos viaja por aquí. Una sucursal con logo puede devolver `null` en este campo y aun así imprimirlo en sus documentos; solo se conserva para no perder el membrete de los talleres migrados."},"workdayMinutes":{"type":"integer","description":"Minutos productivos de una jornada, con los que se calcula la ocupación del taller."},"series":{"type":"string","description":"Serie con la que la sucursal folia sus órdenes de servicio."},"remissionSeries":{"type":"string","description":"Serie con la que la sucursal folia sus remisiones."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","name":"Pitflo Cumbres","slug":"cumbres","code":"CUM","brand":"Pitflo","status":"active","timezone":"America/Monterrey","currency":"MXN","legalName":"Servicio Automotriz Cumbres SA de CV","taxId":"SAC120315M42","taxRegime":"601","addressLine":"Av. Paseo de los Leones 3120","addressNeighborhood":"Cumbres 3er Sector","addressCity":"Monterrey","addressState":"Nuevo León","addressPostalCode":"64610","logoUrl":"https://cdn.pitflo.com/talleres/cumbres/logo.png","workdayMinutes":480,"series":"A","remissionSeries":"R","createdAt":"2026-01-08T15:04:22.000Z","updatedAt":"2026-08-14T16:32:11.000Z"}},"services":{"type":"object","description":"LA ORDEN DE SERVICIO: el documento central del taller, desde que el coche entra hasta que se factura. Trae la posición en el tablero, el estado del presupuesto, la promesa de entrega y todos sus importes en centavos. `stage` es el ÚNICO campo derivado del contrato: la llave canónica de la etapa, comparable entre tableros que nadie nombró igual.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal a la que pertenece la fila. Resuélvela contra el recurso `workshops`."},"boardId":{"type":"string","description":"Tablero al que pertenece la orden."},"columnId":{"type":"string","description":"Columna del tablero donde está la orden ahora mismo."},"stage":{"type":"string","description":"Llave CANÓNICA de la etapa de la columna (`recepcion`, `diagnostico`, `autorizacion`, `reparacion`, `calidad`, `entrega`…). Úsala en vez de `columnId`: no depende de cómo llamó el taller a su columna."},"columnEnteredAt":{"type":"string","format":"date-time","description":"Cuándo entró la orden a la columna actual. La historia completa está en `service_stage_transitions`."},"folio":{"type":"integer","description":"Consecutivo del documento dentro de su sucursal: es como el taller lo cita de viva voz."},"series":{"type":"string","description":"Serie del folio (por omisión \"A\"). El par serie + folio es el nombre humano del documento."},"customerId":{"type":"string","description":"Cliente al que pertenece la fila. Resuélvelo contra el recurso `customers`."},"vehicleId":{"type":"string","description":"Vehículo atendido. Resuélvelo contra el recurso `vehicles`."},"mileage":{"type":"integer","description":"Kilometraje del vehículo al recibirlo."},"fuelLevel":{"type":"integer","description":"Nivel de combustible al recibir, en octavos de tanque (0–8)."},"customerMessage":{"type":"string","description":"Mensaje del taller al cliente que se imprime en el presupuesto y en la remisión."},"tags":{"type":"string","description":"Etiquetas libres de la orden."},"quoteStatus":{"type":"string","description":"Estado del presupuesto: `borrador`, `enviado`, `aprobado`, `rechazado` o `parcial` (nulo si aún no hay presupuesto)."},"quoteStatusAt":{"type":"string","format":"date-time","description":"Cuándo el presupuesto llegó a ese estado."},"validUntil":{"type":"string","format":"date-time","description":"Vigencia del presupuesto. Pasada esta fecha el presupuesto está VENCIDO."},"paymentCondition":{"type":"string","description":"Condición de pago pactada: `contado` o `credito`."},"creditTermDays":{"type":"integer","description":"Días de crédito pactados en esta orden."},"creditDueDate":{"type":"string","format":"date-time","description":"Fecha de vencimiento del crédito de la orden."},"subtotalCents":{"type":"integer","description":"Subtotal de TODAS las líneas, aprobadas o no, en centavos."},"approvedSubtotalCents":{"type":"integer","description":"Subtotal de las líneas APROBADAS por el cliente, en centavos."},"approvedTotalCents":{"type":"integer","description":"Total aprobado con impuestos y descuentos aplicados, en centavos. Es la cifra que el taller espera cobrar."},"taxCents":{"type":"integer","description":"Impuesto trasladado de la orden, en centavos."},"taxRate":{"type":"number","description":"Tasa de impuesto aplicada (0.16 = 16 %)."},"taxIncluded":{"type":"boolean","description":"true = los precios de las líneas YA traen el impuesto dentro."},"discountCents":{"type":"integer","description":"Descuento total de la orden, en centavos."},"discountRate":{"type":"number","description":"Descuento aplicado como tasa (0.10 = 10 %)."},"totalCents":{"type":"integer","description":"Total de la orden con impuestos y descuentos, en centavos."},"invoicedAt":{"type":"string","format":"date-time","description":"Cuándo se facturó la orden; nulo mientras no tenga CFDI. El comprobante completo sale por `fiscal_documents`."},"remissionId":{"type":"string","description":"Remisión con la que se entregó el trabajo, si la hubo."},"globalDocumentId":{"type":"string","description":"Factura global que amparó esta orden, cuando el cliente no pidió comprobante propio."},"sourceAppointmentId":{"type":"string","description":"Cita de la que nació la orden. Resuélvela contra `appointments`."},"originServiceId":{"type":"string","description":"Orden de la que ésta se derivó (una cotización que se partió, por ejemplo)."},"promisedAt":{"type":"string","format":"date-time","description":"Fecha y hora PROMETIDA de entrega al cliente."},"holdReason":{"type":"string","description":"Motivo por el que la orden está detenida: `refaccion`, `autorizacion`, `pago`, `cliente` u `otro`. Nulo si avanza."},"heldAt":{"type":"string","format":"date-time","description":"Desde cuándo está detenida: el reloj de la detención corre desde aquí."},"keysStatus":{"type":"string","description":"Dónde están las llaves del coche: `con_cliente`, `en_tablero` o `entregadas`."},"reworkOfServiceId":{"type":"string","description":"Orden original de la que ésta es RETRABAJO en garantía."},"createdByUserId":{"type":"string","description":"Usuario que creó la fila."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."},"responsibleName":{"type":"string","description":"Quien trajo el vehículo, si no fue el titular. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"responsiblePhone":{"type":"string","description":"Teléfono de quien trajo el vehículo. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"responsibleEmail":{"type":"string","description":"Correo de quien trajo el vehículo. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true}},"example":{"id":"5b8f2d31-6a07-4c92-ae14-73d0b6c85f29","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","boardId":"81c4f0a7-3e29-4d65-b104-9a72c6e8d503","columnId":"d2e6b415-8c73-4092-a5f1-0b3c7d94e618","stage":"reparacion","columnEnteredAt":"2026-08-14T15:10:00.000Z","folio":2841,"series":"A","customerId":"9d4b1e77-2c85-4a13-8f60-5b2e7a9c0d41","vehicleId":"c07e5a26-9134-42db-b8f5-6e1a3c7d9024","mileage":87450,"fuelLevel":3,"customerMessage":"Se recomienda cambiar balatas traseras en la próxima visita.","tags":["garantia_extendida"],"quoteStatus":"aprobado","quoteStatusAt":"2026-08-14T14:05:31.000Z","validUntil":"2026-08-21T05:59:59.000Z","paymentCondition":"contado","creditTermDays":null,"creditDueDate":null,"subtotalCents":894000,"approvedSubtotalCents":742000,"approvedTotalCents":860720,"taxCents":118720,"taxRate":0.16,"taxIncluded":false,"discountCents":0,"discountRate":null,"totalCents":1037040,"invoicedAt":null,"remissionId":null,"globalDocumentId":null,"sourceAppointmentId":"6f1d8b03-4a97-42e5-9c86-2d5b7e0a3f14","originServiceId":null,"promisedAt":"2026-08-15T22:00:00.000Z","holdReason":null,"heldAt":null,"keysStatus":"en_tablero","reworkOfServiceId":null,"createdByUserId":"0a3c7e91-5d26-4b80-9f13-8c4e6a2b7d05","createdAt":"2026-08-14T13:47:09.000Z","updatedAt":"2026-08-14T16:32:11.000Z","responsibleName":"María Fernanda Treviño","responsiblePhone":"+528112345678","responsibleEmail":"mf.trevino@example.mx"}},"service_items":{"type":"object","description":"Las LÍNEAS de la orden: refacciones, mano de obra y servicios de catálogo, cada una con su precio, su costo, su decisión del cliente y su garantía. Una línea puede colgar de otra (`parentItemId`) cuando es parte de un paquete.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal a la que pertenece la fila. Resuélvela contra el recurso `workshops`."},"serviceId":{"type":"string","description":"Orden de servicio a la que pertenece la fila. Resuélvela contra el recurso `services`."},"parentItemId":{"type":"string","description":"Línea padre cuando ésta es parte de un paquete o de un servicio compuesto."},"kind":{"type":"string","description":"Tipo de línea: `product` (refacción o producto del catálogo, con `productId`), `labor` (mano de obra, con `laborHours` y `laborRateCents`) o `service` (servicio de catálogo, con `serviceTemplateId`). No hay valor «manual»: una línea es manual cuando no trae `productId` ni `serviceTemplateId`."},"description":{"type":"string","description":"Descripción de la línea tal como la lee el cliente en el presupuesto."},"quantity":{"type":"number","description":"Cantidad de la línea (piezas, litros u horas según el tipo)."},"unitPriceCents":{"type":"integer","description":"Precio unitario de venta, en centavos."},"unitCostCents":{"type":"integer","description":"Costo unitario del taller, en centavos. Es SU dato, por eso viaja."},"priceBeforeMarginCents":{"type":"integer","description":"Precio que la línea tenía antes de que el motor de margen lo ajustara."},"discountPct":{"type":"number","description":"Descuento de la línea como porcentaje."},"discountCents":{"type":"integer","description":"Descuento de la línea en centavos."},"totalCents":{"type":"integer","description":"Total de la línea ya con su descuento, en centavos."},"satCode":{"type":"string","description":"Clave de producto/servicio del SAT con la que se timbra la línea."},"priority":{"type":"string","description":"Urgencia técnica sugerida: `critica`, `recomendada` u `opcional`."},"approvalStatus":{"type":"string","description":"Decisión del cliente sobre la línea: `pendiente`, `aprobada`, `rechazada` o `pospuesta`."},"approvalSource":{"type":"string","description":"Por dónde llegó la decisión: `mostrador`, `liga`, `whatsapp` o `telefono`."},"approvalDecidedAt":{"type":"string","format":"date-time","description":"Cuándo se decidió la línea."},"approvalDecidedByUserId":{"type":"string","description":"Usuario del taller que registró la decisión (nulo si la tomó el cliente por la liga)."},"approvalNote":{"type":"string","description":"Nota que acompañó la decisión."},"productId":{"type":"string","description":"Producto del catálogo al que se refiere la fila. Resuélvelo contra el recurso `products`."},"serviceTemplateId":{"type":"string","description":"Plantilla de servicio de la que nació la línea. Resuélvela contra `service_templates`."},"pricingType":{"type":"string","description":"Cómo se precia la línea: `fijo` o `por_hora`."},"laborHours":{"type":"number","description":"Horas de mano de obra de la línea."},"laborRateCents":{"type":"integer","description":"Tarifa de mano de obra por hora aplicada, en centavos."},"laborIncluded":{"type":"boolean","description":"`true` cuando el importe de la línea YA trae su mano de obra dentro: un producto que se vende instalado es UNA sola línea, y su `totalCents` es cantidad × `unitPriceCents` menos su descuento MÁS `laborHours` × `laborRateCents`. El descuento de la línea se aplica solo a la pieza. En cualquier otra línea es `false` y `laborHours`/`laborRateCents` son informativos."},"position":{"type":"number","description":"Orden de la línea dentro de la orden, para reimprimirla igual."},"notes":{"type":"string","description":"Nota libre que capturó el taller en el documento."},"isSublet":{"type":"boolean","description":"true = el trabajo se TERCERIZÓ con un proveedor externo."},"subletSupplierId":{"type":"string","description":"Proveedor que hizo el trabajo tercerizado."},"subletCostCents":{"type":"integer","description":"Lo que el tercero le cobró al taller, en centavos."},"subletExpenseId":{"type":"string","description":"Gasto que ese trabajo tercerizado generó. Resuélvelo contra `expenses`."},"warrantyMonths":{"type":"integer","description":"Meses de garantía pactados en la línea."},"warrantyKm":{"type":"integer","description":"Kilómetros de garantía pactados en la línea."},"approvalReason":{"type":"string","description":"Motivo que dio el cliente al rechazar o posponer."},"approvalAmountCents":{"type":"integer","description":"Importe SELLADO al momento de decidir, en centavos: lo que se ganó o se perdió ese día, aunque el precio cambie después."},"deferredUntil":{"type":"string","format":"date-time","description":"Hasta cuándo pospuso el cliente esta línea: es la fecha del seguimiento."},"saleCategoryId":{"type":"string","description":"Categoría de VENTA sellada en el renglón, del árbol de servicios: la del paquete si este renglón cuelga de un servicio (`parentItemId`), la de su plantilla, o la categoría de venta de la subcategoría del producto vendido suelto. Es la que agrupan los reportes de ventas; se sella al crear la línea y no cambia con la orden ya entregada. Resuélvela contra `service_categories`. Nula solo en renglones anteriores al sellado."},"saleSubcategoryId":{"type":"string","description":"Subcategoría de VENTA sellada, forzosamente de `saleCategoryId`. Resuélvela contra `service_subcategories`."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"e4a71c58-2039-4bd6-8f47-1c9e5b30a726","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","serviceId":"5b8f2d31-6a07-4c92-ae14-73d0b6c85f29","parentItemId":null,"kind":"product","description":"Balatas delanteras cerámicas (juego)","quantity":1,"unitPriceCents":189000,"unitCostCents":112000,"priceBeforeMarginCents":175000,"discountPct":null,"discountCents":0,"totalCents":189000,"satCode":"25172504","priority":"critica","approvalStatus":"aprobada","approvalSource":"liga","approvalDecidedAt":"2026-08-14T14:05:31.000Z","approvalDecidedByUserId":null,"approvalNote":"Autorizado por WhatsApp.","productId":"b6d0937f-51ac-4e28-9370-4f8a1c62d5be","serviceTemplateId":null,"pricingType":"fijo","laborHours":null,"laborRateCents":null,"laborIncluded":false,"position":1,"notes":"Se entrega la refacción usada al cliente.","isSublet":false,"subletSupplierId":null,"subletCostCents":null,"subletExpenseId":null,"warrantyMonths":6,"warrantyKm":20000,"approvalReason":null,"approvalAmountCents":189000,"deferredUntil":null,"saleCategoryId":"7d1e9b42-0c35-4a68-9f27-3b8a6c5d1e04","saleSubcategoryId":"a2f6c815-3e97-4b20-8d41-6c0b9e7f3a58","createdAt":"2026-08-14T13:52:40.000Z","updatedAt":"2026-08-14T14:05:31.000Z"}},"service_stage_transitions":{"type":"object","description":"La BITÁCORA DE ETAPAS del tablero: un renglón por cada vez que una orden entró a una columna. Libro append-only con una sola mutación posterior — `leftAt`, que se sella en el MISMO instante en que nace la fila siguiente de esa orden. Por eso paginar por `createdAt` no pierde nada: el `leftAt` de una fila es el `enteredAt` de la siguiente, y la fila sin `leftAt` es la etapa donde la orden está ahora.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal a la que pertenece la fila. Resuélvela contra el recurso `workshops`."},"serviceId":{"type":"string","description":"Orden de servicio a la que pertenece la fila. Resuélvela contra el recurso `services`."},"fromColumnId":{"type":"string","description":"Columna de la que salió la orden; nula en la primera etapa."},"toColumnId":{"type":"string","description":"Columna a la que entró la orden. Su llave canónica es la que `services.stage` publica."},"actorUserId":{"type":"string","description":"Usuario que movió la tarjeta."},"reason":{"type":"string","description":"Cómo se movió: `manual`, `automatico` o `regla`."},"note":{"type":"string","description":"Nota del actor al mover («el cliente pidió esperar a la quincena»)."},"enteredAt":{"type":"string","format":"date-time","description":"Cuándo entró la orden a esta etapa."},"leftAt":{"type":"string","format":"date-time","description":"Cuándo salió de esta etapa; nulo mientras siga en ella. `leftAt − enteredAt` es el tiempo en etapa."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."}},"example":{"id":"7c3e0d92-4b18-4a67-b503-e91d6c8f2740","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","serviceId":"5b8f2d31-6a07-4c92-ae14-73d0b6c85f29","fromColumnId":"a05f7c31-9d24-4e80-8b16-3f7c2e5a9014","toColumnId":"d2e6b415-8c73-4092-a5f1-0b3c7d94e618","actorUserId":"0a3c7e91-5d26-4b80-9f13-8c4e6a2b7d05","reason":"manual","note":"Cliente autorizó por la liga; pasa a reparación.","enteredAt":"2026-08-14T15:10:00.000Z","leftAt":null,"createdAt":"2026-08-14T15:10:00.000Z"}},"customers":{"type":"object","description":"El EXPEDIENTE DE CLIENTES de la empresa entera: se comparte entre sucursales a propósito, así que `workshop={id}` NO lo filtra. Los datos FISCALES del cliente no viven aquí y no son recurso de /v1: el receptor congelado de cada comprobante sale en `fiscal_documents`.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"homeWorkshopId":{"type":"string","description":"Sucursal donde el cliente se dio de alta. No limita su visibilidad: el expediente es de la empresa."},"isFleet":{"type":"boolean","description":"true = cliente de FLOTA (empresa con varias unidades); false = particular."},"paymentCondition":{"type":"string","description":"Condición de pago por defecto del cliente: `contado` o `credito`."},"creditDays":{"type":"integer","description":"Días de crédito autorizados al cliente."},"tags":{"type":"string","description":"Etiquetas libres del cliente."},"consents":{"type":"array","items":{"type":"object"},"description":"Los CONSENTIMIENTOS del cliente, uno por tipo sobre el que decidió: `key` (la clave del catálogo `consent_types`), `name`, `granted` (true SOLO con el sí vigente), `status` (`otorgado`, `negado`, `retirado`, `desactualizado` = el texto cambió desde que decidió, `vencido` = la vigencia se cumplió), `version` (la del texto que decidió), `decidedAt`, `expiresAt` (nulo = no vence) y `active` (false = el tipo dejó de pedirse, la decisión sigue valiendo). Un tipo sin decisión NO aparece: ausencia = sin respuesta. Quién decidió no viaja. Filtra con `consent=<key>` / `consent=<key>:false`."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."},"name":{"type":"string","description":"Nombre del cliente o razón social comercial con la que el taller lo llama. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"email":{"type":"string","description":"Correo del cliente. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"phone":{"type":"string","description":"Teléfono fijo del cliente. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"mobile":{"type":"string","description":"Móvil del cliente: es por donde viajan los avisos y la liga del presupuesto. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"addressLine":{"type":"string","description":"Calle y número. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"addressNeighborhood":{"type":"string","description":"Colonia. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"addressCity":{"type":"string","description":"Municipio o ciudad. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"addressState":{"type":"string","description":"Estado. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"addressPostalCode":{"type":"string","description":"Código postal. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"notes":{"type":"string","description":"Nota libre del taller SOBRE LA PERSONA: por eso cuenta como dato personal. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true}},"example":{"id":"9d4b1e77-2c85-4a13-8f60-5b2e7a9c0d41","homeWorkshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","isFleet":false,"paymentCondition":"contado","creditDays":null,"tags":["frecuente"],"createdAt":"2025-11-03T18:20:44.000Z","updatedAt":"2026-08-14T13:47:09.000Z","name":"José Ramón Villarreal","email":"jr.villarreal@example.mx","phone":"+528183451200","mobile":"+528118765432","addressLine":"Río Danubio 218","addressNeighborhood":"Del Valle","addressCity":"San Pedro Garza García","addressState":"Nuevo León","addressPostalCode":"66220","notes":"Prefiere que le llamen antes de las 10 a. m.","consents":[{"key":"marketing","name":"Promociones y recordatorios","granted":true,"status":"otorgado","version":2,"decidedAt":"2026-09-12T16:05:31.000Z","expiresAt":null,"active":true}]}},"vehicles":{"type":"object","description":"Los VEHÍCULOS del expediente, colgados de su cliente. Como `customers`, son de la empresa entera y `workshop={id}` no los filtra. La placa, el VIN y el número de flota identifican al dueño tan bien como su nombre: van marcados como dato personal.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"customerId":{"type":"string","description":"Cliente al que pertenece la fila. Resuélvelo contra el recurso `customers`."},"make":{"type":"string","description":"Marca del vehículo, en texto, tal como quedó capturada."},"model":{"type":"string","description":"Modelo del vehículo, en texto."},"makeId":{"type":"string","description":"Marca en el catálogo vehicular global de Pitflo (id de plataforma, sin dato del cliente)."},"modelId":{"type":"string","description":"Modelo en el catálogo vehicular global."},"engineSpecId":{"type":"string","description":"Versión de motor en el catálogo vehicular global."},"year":{"type":"integer","description":"Año del vehículo."},"color":{"type":"string","description":"Color del vehículo, en texto: la etiqueta del catálogo cuando `colorKey` viene, o lo que capturó el taller cuando no."},"colorKey":{"type":"string","description":"Clave del catálogo de colores: `blanco`, `negro`, `gris`, `plata`, `gris_oxford`, `rojo`, `azul`, `azul_marino`, `verde`, `amarillo`, `naranja`, `cafe`, `beige`, `arena`, `dorado`, `vino`, `morado` o `rosa`. `null` = color libre («Otro»)."},"engineCode":{"type":"string","description":"Código de motor capturado por el taller."},"transmission":{"type":"string","description":"Transmisión: `manual`, `automatica` o `cvt`."},"fuelType":{"type":"string","description":"Combustible: `gasolina`, `diesel`, `hibrido` o `electrico`."},"mileage":{"type":"integer","description":"Último kilometraje conocido del vehículo."},"notes":{"type":"string","description":"Nota libre sobre el vehículo."},"customerConsents":{"type":"array","items":{"type":"object"},"description":"Los consentimientos DEL CLIENTE DUEÑO del vehículo, con la misma forma que `customers.consents`. El responsable de la unidad no es una entidad: el consentimiento es del cliente, y aquí viaja para que no tengas que cruzar dos recursos. Filtra con `consent=<key>` / `consent=<key>:false`."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."},"plate":{"type":"string","description":"Placa del vehículo. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"vin":{"type":"string","description":"Número de serie (VIN). Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"fleetNumber":{"type":"string","description":"Número económico dentro de la flota del cliente. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"responsibleName":{"type":"string","description":"Responsable fijo de la unidad en una flota. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"responsiblePhone":{"type":"string","description":"Teléfono del responsable de la unidad. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"responsibleEmail":{"type":"string","description":"Correo del responsable de la unidad. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true}},"example":{"id":"c07e5a26-9134-42db-b8f5-6e1a3c7d9024","customerId":"9d4b1e77-2c85-4a13-8f60-5b2e7a9c0d41","make":"Nissan","model":"Versa","makeId":"1f7a3c50-8b62-4d19-9e07-2c4b8f6a1d35","modelId":"4b9e2d17-6c30-4f85-a231-9e60c7d43a85","engineSpecId":"a8c5f312-7e94-4b06-8d2f-51a7c3e9b640","year":2021,"color":"Gris Oxford","colorKey":"gris_oxford","engineCode":"HR16DE","transmission":"automatica","fuelType":"gasolina","mileage":87450,"notes":"Rines de aluminio con birlo de seguridad; llave en la guantera.","createdAt":"2025-11-03T18:24:02.000Z","updatedAt":"2026-08-14T13:47:09.000Z","plate":"SXH-482-C","vin":"3N1CN8AD5ML830472","fleetNumber":null,"responsibleName":null,"responsiblePhone":null,"responsibleEmail":null,"customerConsents":[{"key":"marketing","name":"Promociones y recordatorios","granted":true,"status":"otorgado","version":2,"decidedAt":"2026-09-12T16:05:31.000Z","expiresAt":null,"active":true}]}},"consent_types":{"type":"object","description":"El CATÁLOGO de tipos de consentimiento de la empresa (solo lectura): las claves que `customers.consents` y `vehicles.customerConsents` publican, con la versión vigente del texto y su vigencia. Cada empresa escribe sus propias claves (`marketing`, `whatsapp`…); no hay un vocabulario fijo de Pitflo. El texto que el cliente leyó no viaja: queda sellado en cada decisión. Es de la empresa entera: `workshop=` no aplica.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"key":{"type":"string","description":"Clave estable del tipo (`[a-z0-9_]{2,40}`), la que escribió la empresa. Inmutable: es la que usas en `consent=<key>`."},"name":{"type":"string","description":"Nombre del consentimiento, como lo ve el cliente."},"version":{"type":"integer","description":"Versión VIGENTE del texto. Sube cada vez que la empresa lo cambia; una decisión sobre una versión anterior se publica como `desactualizado` y se vuelve a pedir."},"validityMonths":{"type":"integer","description":"Vigencia del sí, en meses; nulo = no vence hasta que el cliente lo retire."},"active":{"type":"boolean","description":"true = se sigue pidiendo; false = dejó de pedirse (las decisiones ya tomadas siguen valiendo y siguen publicándose)."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"e4a7c2d9-5b13-4f80-9a6e-2d18c7b3f054","key":"marketing","name":"Promociones y recordatorios","version":2,"validityMonths":null,"active":true,"createdAt":"2026-09-10T15:00:00.000Z","updatedAt":"2026-09-12T15:40:12.000Z"}},"appointments":{"type":"object","description":"Las CITAS de la agenda: las del mostrador y las que entran por la liga pública o por un sistema externo. Una cita se convierte en orden (`convertedServiceId`) y ahí termina su ciclo. Cuando el cliente todavía no existe en el expediente, sus datos viajan en los campos `prospect*`.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal a la que pertenece la fila. Resuélvela contra el recurso `workshops`."},"customerId":{"type":"string","description":"Cliente al que pertenece la fila. Resuélvelo contra el recurso `customers`."},"vehicleId":{"type":"string","description":"Vehículo de la cita, si ya está en el expediente."},"scheduledAt":{"type":"string","format":"date-time","description":"Fecha y hora de la cita, en UTC."},"durationMin":{"type":"integer","description":"Duración reservada en la agenda, en minutos."},"reason":{"type":"string","description":"Motivo de la visita, en palabras del cliente."},"notes":{"type":"string","description":"Nota del mostrador sobre la cita."},"status":{"type":"string","description":"Estado de la cita: `agendada`, `confirmada`, `llego`, `cancelada`, `no_asistio` o `convertida`."},"confirmedAt":{"type":"string","format":"date-time","description":"Cuándo la confirmó el TALLER."},"confirmedByClientAt":{"type":"string","format":"date-time","description":"Cuándo la confirmó EL CLIENTE por la liga pública."},"arrivedAt":{"type":"string","format":"date-time","description":"Cuándo llegó el cliente."},"cancelledAt":{"type":"string","format":"date-time","description":"Fecha de cancelación, en UTC. Nula mientras el documento esté vivo: un documento cancelado NO desaparece de esta API, se marca."},"cancelReason":{"type":"string","description":"Motivo de la cancelación."},"noShowAt":{"type":"string","format":"date-time","description":"Cuándo se marcó como no asistió."},"convertedServiceId":{"type":"string","description":"Orden de servicio que nació de esta cita."},"convertedAt":{"type":"string","format":"date-time","description":"Cuándo se convirtió en orden."},"externalId":{"type":"string","description":"Identidad de la cita en el sistema externo que la creó: tu llave para no duplicarla."},"source":{"type":"string","description":"De dónde vino la cita (`liga`, `whatsapp`, `api`…). Nulo = mostrador."},"bayKind":{"type":"string","description":"Tipo de bahía que va a necesitar el trabajo (`rampa`, `alineacion`, `pintura`…). Nulo = no se pidió ninguno."},"bayId":{"type":"string","description":"Bahía RESERVADA por el asesor. Reservar no la aparta físicamente: al llegar el coche, la orden nace en su cola."},"createdByUserId":{"type":"string","description":"Usuario que creó la fila."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."},"prospectName":{"type":"string","description":"Nombre de quien agendó cuando todavía no es cliente del expediente. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"prospectPhone":{"type":"string","description":"Teléfono del prospecto. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"prospectEmail":{"type":"string","description":"Correo del prospecto. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"prospectVehicle":{"type":"string","description":"Vehículo que el prospecto describió en texto. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true}},"example":{"id":"6f1d8b03-4a97-42e5-9c86-2d5b7e0a3f14","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","customerId":"9d4b1e77-2c85-4a13-8f60-5b2e7a9c0d41","vehicleId":"c07e5a26-9134-42db-b8f5-6e1a3c7d9024","scheduledAt":"2026-08-14T14:00:00.000Z","durationMin":60,"reason":"Rechinido al frenar","notes":"Llega temprano, deja el coche todo el día.","status":"convertida","confirmedAt":"2026-08-13T17:02:10.000Z","confirmedByClientAt":"2026-08-13T16:55:47.000Z","arrivedAt":"2026-08-14T13:44:52.000Z","cancelledAt":null,"cancelReason":null,"noShowAt":null,"convertedServiceId":"5b8f2d31-6a07-4c92-ae14-73d0b6c85f29","convertedAt":"2026-08-14T13:47:09.000Z","externalId":null,"source":"liga","createdByUserId":"0a3c7e91-5d26-4b80-9f13-8c4e6a2b7d05","createdAt":"2026-08-12T19:31:05.000Z","updatedAt":"2026-08-14T13:47:09.000Z","prospectName":null,"prospectPhone":null,"prospectEmail":null,"prospectVehicle":null,"bayKind":"alineacion","bayId":null}},"products":{"type":"object","description":"El CATÁLOGO DE PRODUCTOS: refacciones e insumos con su precio de venta, su costo de referencia y su mínimo. Todos los importes van SIN IVA. La fila con `workshopId` nulo es el producto corporativo, válido en toda sucursal — si filtras por sucursal, esas filas siguen viniendo, porque perderlas sería mentirte.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal dueña del producto; NULO = producto corporativo de toda la empresa."},"categoryId":{"type":"string","description":"Categoría del producto (obligatoria). Resuélvela contra `product_categories`."},"subcategoryId":{"type":"string","description":"Subcategoría del producto (obligatoria), forzosamente de `categoryId`. Resuélvela contra `product_subcategories`; ahí está la categoría de VENTA a la que suma el producto vendido suelto."},"supplierId":{"type":"string","description":"Proveedor preferente del producto."},"name":{"type":"string","description":"Nombre del producto."},"sku":{"type":"string","description":"Clave interna del producto en el taller."},"satCode":{"type":"string","description":"Clave de producto/servicio del SAT con la que se timbra. Vacía significa que el producto la HEREDA de su subcategoría o de su categoría."},"description":{"type":"string","description":"Descripción del producto."},"baseUnit":{"type":"string","description":"Unidad BASE en la que se lleva la existencia (`pieza`, `litro`, `metro`…). Todas las cantidades de esta API están en milésimas de esta unidad."},"tracked":{"type":"boolean","description":"true = el producto lleva existencia y mueve kardex; false = se vende sin controlar inventario."},"salePriceCents":{"type":"integer","description":"Precio de venta por unidad base, en centavos y SIN IVA. Si el taller captura sus precios con el impuesto dentro, la conversión ocurre al capturar: aquí siempre llega neto."},"referenceCostCents":{"type":"integer","description":"Costo de referencia por unidad base, en centavos y SIN IVA. El costo REAL de cada capa está en `inventory_movements`."},"minStockMilli":{"type":"integer","description":"Mínimo de existencia en milésimas: por debajo, el producto entra a la lista de críticos."},"maintainMargin":{"type":"boolean","description":"true = al recibir mercancía más cara, el precio de venta se sube solo para sostener el margen."},"needsReview":{"type":"boolean","description":"true = el producto quedó marcado para que alguien lo revise (precio o costo sospechoso)."},"isActive":{"type":"boolean","description":"false = el producto ya no se vende, pero su historia se conserva."},"specialOrder":{"type":"boolean","description":"true = refacción SOBRE PEDIDO: todo lo que se recibió de ella salió directo a las órdenes que la pidieron y su existencia quedó en cero. No cuenta en mínimos, rotación ni valor del inventario, y se apaga sola con cualquier entrada al anaquel (compra, ajuste, conteo o «pasar a inventario»). El reparto que la produjo está en `purchase_order_requests`."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"b6d0937f-51ac-4e28-9370-4f8a1c62d5be","workshopId":null,"categoryId":"5a7c1e38-4d92-4067-b85f-2e6a0c3d9147","subcategoryId":"c3f8b520-7a41-4e96-8d03-51b7e2c4a068","supplierId":"8e2c6a04-9f71-4b35-8d60-3a15c7e94b28","name":"Balatas delanteras cerámicas Versa 2019-2023","sku":"BAL-NIS-VER-D","satCode":"25172504","description":"Juego de 4 balatas cerámicas con herrajes.","baseUnit":"pieza","tracked":true,"salePriceCents":189000,"referenceCostCents":112000,"minStockMilli":4000,"maintainMargin":true,"needsReview":false,"isActive":true,"specialOrder":false,"createdAt":"2025-06-11T17:03:29.000Z","updatedAt":"2026-08-10T21:14:05.000Z"}},"product_categories":{"type":"object","description":"El primer nivel del ÁRBOL DE PRODUCTOS: cómo el taller clasifica su catálogo de refacciones («Filtros», «Frenos», «Aceite Motor»…). `products.categoryId` apunta aquí. Toda empresa nace con el árbol de fábrica y lo renombra, reordena, amplía o desactiva; la fila con `brandItemId` la dicta la marca de la red. Solo lectura.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal dueña; NULO = de la empresa, visible en toda sucursal. Con sucursal es «solo esta sucursal»."},"name":{"type":"string","description":"Nombre de la categoría como lo lee el taller."},"description":{"type":"string","description":"Descripción libre de la categoría."},"satCode":{"type":"string","description":"Clave de producto/servicio del SAT que HEREDAN los productos de aquí cuando no traen la suya (la subcategoría gana a la categoría; el producto, a las dos)."},"color":{"type":"string","description":"Color de la categoría en la pantalla, como token del sistema de diseño (`slate`, `amber`, `rose`…), nunca un hex."},"position":{"type":"number","description":"Orden en que el taller la acomodó (0 = primera). Es el orden de la pantalla y de los reportes."},"isActive":{"type":"boolean","description":"false = ya no se ofrece al clasificar, pero lo ya clasificado la conserva."},"brandNetworkId":{"type":"string","description":"Red de franquicia cuya MARCA dicta esta fila; NULO = la creó el taller. Con marca, el taller no la renombra ni la retira: solo la ordena u oculta."},"brandItemId":{"type":"string","description":"Id de la fila en el árbol de la marca: el mismo en todas las sucursales de la red, para comparar entre talleres."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"5a7c1e38-4d92-4067-b85f-2e6a0c3d9147","workshopId":null,"name":"Frenos","description":"Balatas, discos, tambores y zapatas.","satCode":"25172504","color":"rose","position":8,"isActive":true,"brandNetworkId":null,"brandItemId":null,"createdAt":"2026-09-11T15:02:10.000Z","updatedAt":"2026-09-11T15:02:10.000Z"}},"product_subcategories":{"type":"object","description":"El segundo nivel del ÁRBOL DE PRODUCTOS («Frenos → Balatas / Zapatas DEL»). `products.subcategoryId` apunta aquí y toda subcategoría cuelga de UNA categoría (`categoryId`). Trae la CATEGORÍA DE VENTA (`saleCategoryId`/`saleSubcategoryId`, del árbol de SERVICIOS): a qué categoría suma un producto de aquí cuando se vende SUELTO —un filtro suelto suma en «02 ADICIONALES / Filtros»; el mismo filtro dentro de una afinación suma donde manda el paquete—. Es lo que queda sellado en `service_items.saleCategoryId`. Solo lectura.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal dueña; NULO = de la empresa, visible en toda sucursal. Con sucursal es «solo esta sucursal»."},"categoryId":{"type":"string","description":"Categoría de producto a la que pertenece. Resuélvela contra `product_categories`."},"name":{"type":"string","description":"Nombre de la subcategoría como lo lee el taller."},"description":{"type":"string","description":"Descripción libre de la subcategoría."},"satCode":{"type":"string","description":"Clave de producto/servicio del SAT que HEREDAN los productos de aquí cuando no traen la suya (la subcategoría gana a la categoría; el producto, a las dos)."},"position":{"type":"number","description":"Orden en que el taller la acomodó (0 = primera). Es el orden de la pantalla y de los reportes."},"isActive":{"type":"boolean","description":"false = ya no se ofrece al clasificar, pero lo ya clasificado la conserva."},"saleCategoryId":{"type":"string","description":"Categoría de VENTA: la categoría de SERVICIOS a la que suma un producto de aquí vendido suelto. Resuélvela contra `service_categories`."},"saleSubcategoryId":{"type":"string","description":"Subcategoría de VENTA, forzosamente de `saleCategoryId`. Resuélvela contra `service_subcategories`."},"brandNetworkId":{"type":"string","description":"Red de franquicia cuya MARCA dicta esta fila; NULO = la creó el taller. Con marca, el taller no la renombra ni la retira: solo la ordena u oculta."},"brandItemId":{"type":"string","description":"Id de la fila en el árbol de la marca: el mismo en todas las sucursales de la red, para comparar entre talleres."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"c3f8b520-7a41-4e96-8d03-51b7e2c4a068","workshopId":null,"categoryId":"5a7c1e38-4d92-4067-b85f-2e6a0c3d9147","name":"Balatas / Zapatas DEL","description":null,"satCode":null,"position":0,"isActive":true,"saleCategoryId":"7d1e9b42-0c35-4a68-9f27-3b8a6c5d1e04","saleSubcategoryId":"a2f6c815-3e97-4b20-8d41-6c0b9e7f3a58","brandNetworkId":null,"brandItemId":null,"createdAt":"2026-09-11T15:02:10.000Z","updatedAt":"2026-09-11T15:02:10.000Z"}},"inventory_stock":{"type":"object","description":"La EXISTENCIA viva por producto y sucursal: cuánto hay y cuánto vale ahora mismo. Es la foto; la película —quién movió qué y a qué costo— está en `inventory_movements`.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal a la que pertenece la fila. Resuélvela contra el recurso `workshops`."},"productId":{"type":"string","description":"Producto del catálogo al que se refiere la fila. Resuélvelo contra el recurso `products`."},"onHandMilli":{"type":"integer","description":"Existencia actual en milésimas de la unidad base (1000 = 1 pieza). Puede ser negativa si el taller autorizó una salida en descubierto."},"valueCents":{"type":"integer","description":"Valuación de esa existencia en centavos, con el costo real de las capas PEPS vivas."},"lastCostCents":{"type":"integer","description":"Último costo unitario con el que entró mercancía, en centavos."},"lastMovementAt":{"type":"string","format":"date-time","description":"Cuándo se movió por última vez esta existencia."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"0d6b3f81-92c5-4a07-8e14-7f2c5a90b634","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","productId":"b6d0937f-51ac-4e28-9370-4f8a1c62d5be","onHandMilli":7000,"valueCents":784000,"lastCostCents":112000,"lastMovementAt":"2026-08-14T16:02:55.000Z","createdAt":"2025-06-11T17:03:29.000Z","updatedAt":"2026-08-14T16:02:55.000Z"}},"inventory_movements":{"type":"object","description":"EL KARDEX: un renglón por cada vez que el inventario se movió, con el saldo y el valor que quedaron DESPUÉS del asiento. Libro append-only (pagina por `createdAt`): un asiento no se edita ni se borra — se CONTRA-ASIENTA con otra fila, que te llega como página nueva. `totalCostCents` es lo que el asiento movió en dinero, con el mismo signo que la cantidad; el COGS de una orden es la suma NEGADA de los `totalCostCents` de sus asientos.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal a la que pertenece la fila. Resuélvela contra el recurso `workshops`."},"productId":{"type":"string","description":"Producto del catálogo al que se refiere la fila. Resuélvelo contra el recurso `products`."},"kind":{"type":"string","description":"Qué clase de asiento es: `compra` (entrada por recepción de compra: abre una capa de costo), `consumo` (salida a una orden de servicio: la refacción que el almacenista entrega, con `serviceId` y `serviceItemId`), `reingreso` (vuelve al almacén algo que salió a una orden; apunta al consumo que revierte), `ajuste` (corrección con motivo de catálogo, `reasonId`), `regularizacion` (salda un descubierto: mueve dinero sin mover materia), `reversa` (deshace otro asiento, `reversesMovementId`) o `devolucion_proveedor` (salida hacia el proveedor, `supplierReturnId`)."},"quantityMilli":{"type":"integer","description":"LO ÚNICO que mueve el saldo: milésimas SIGNADAS (positivo entra, negativo sale). Cero solo en `regularizacion`, que mueve dinero sin mover materia."},"enteredQuantityMilli":{"type":"integer","description":"Lo que el usuario capturó, en la unidad que eligió (p. ej. 1 tambo), también en milésimas."},"purchaseUnitId":{"type":"string","description":"Unidad de compra que el usuario eligió al capturar; nula si capturó en la unidad base."},"conversionFactorMilli":{"type":"integer","description":"Factor CONGELADO de esa unidad a la base, en milésimas (1000 = 1:1). Congelarlo es lo que permite reimprimir el asiento aunque el catálogo cambie."},"unitCostCents":{"type":"integer","description":"Costo por unidad BASE, sin IVA. En una entrada es el de la capa; en una salida es informativo (el costo exacto son sus consumos de capa)."},"enteredUnitCostCents":{"type":"integer","description":"Costo por unidad de COMPRA, tal como venía en la factura del proveedor y sin IVA."},"totalCostCents":{"type":"integer","description":"Dinero que el asiento movió, con el MISMO signo que la cantidad. `valueCents += totalCostCents` no tiene una sola excepción: eso es lo que hace verificable la valuación."},"costEstimated":{"type":"boolean","description":"true = parte del costo de este asiento no salió de una capa real, sino de un estimador. El reporte de margen lo declara y nunca lo esconde."},"forcedNegative":{"type":"boolean","description":"true = fue una salida AUTORIZADA por encima de la existencia, con motivo obligatorio."},"uncoveredMilli":{"type":"integer","description":"Milésimas que salieron SIN capa que las respalde: la deuda contable del asiento."},"uncoveredSettledMilli":{"type":"integer","description":"Cuánto de esa deuda ya saldó una entrada posterior."},"balanceAfterMilli":{"type":"integer","description":"Existencia que quedó DESPUÉS del asiento, en milésimas. Es la cadena que permite decir dónde empezó una deriva, no solo que existe."},"balanceAfterValueCents":{"type":"integer","description":"Valuación que quedó DESPUÉS del asiento, en centavos."},"serviceId":{"type":"string","description":"Orden de servicio a la que pertenece la fila. Resuélvela contra el recurso `services`."},"serviceItemId":{"type":"string","description":"Línea de la orden que consumió la refacción. Resuélvela contra `service_items`."},"issueOrigin":{"type":"string","description":"Cómo se originó la salida a la orden: `mostrador` (el almacenista apretó «surtir»), `cierre` (el surtido automático que corre dentro de la transacción de la factura o la remisión) o `sobre_pedido` (la pieza salió a la orden en la MISMA transacción de la recepción que la trajo, sin pasar por el anaquel; el asiento lleva `purchaseOrderRequestId`). Solo en asientos `consumo`."},"returnReason":{"type":"string","description":"Por qué volvió la pieza (solo en `reingreso`): `no_utilizada` (sobró o el trabajo salió sin ella), `linea_rechazada` (el cliente rechazó o pospuso la línea), `error_de_captura` (se surtió lo que no era), `defectuosa` (salió mala: reingresa y sale de inmediato por ajuste, nunca queda vendible) o `nota_credito` (el cliente la regresó con la nota de crédito)."},"receiptId":{"type":"string","description":"Recepción que originó el asiento. Resuélvela contra `inventory_receipts`."},"countId":{"type":"string","description":"Conteo físico que originó el ajuste. Resuélvelo contra `inventory_counts`."},"countLineId":{"type":"string","description":"Línea del conteo que produjo exactamente este ajuste."},"supplierReturnId":{"type":"string","description":"Devolución a proveedor que sacó estas piezas. Resuélvela contra `supplier_returns`."},"purchaseOrderRequestId":{"type":"string","description":"La partida de orden de compra que lo surtió DIRECTO (refacción sobre pedido): la `compra` y el `consumo` del par llevan el mismo id y el asiento no pasó por el anaquel. Nulo si no vino de una OC sobre pedido. Resuélvela contra `purchase_order_requests`."},"reasonId":{"type":"string","description":"Motivo de catálogo del ajuste. Obligatorio en todo `ajuste` y en toda salida forzada: una venta no se «justifica», un ajuste sí."},"notes":{"type":"string","description":"Nota libre del asiento."},"salePriceBeforeCents":{"type":"integer","description":"Precio de venta que el producto tenía ANTES de que esta compra lo moviera (solo bajo «sostener el margen»)."},"salePriceAfterCents":{"type":"integer","description":"Precio de venta que quedó DESPUÉS de esta compra."},"reversesMovementId":{"type":"string","description":"Asiento que este asiento deshace, entero o en parte."},"reversedMilli":{"type":"integer","description":"Cuánto de ESTE asiento ya se revirtió, en milésimas."},"regularizesMovementId":{"type":"string","description":"Asiento en descubierto que esta `regularizacion` salda."},"cancelledAt":{"type":"string","format":"date-time","description":"Sello de cancelación. NO resta: la resta la hace el contra-asiento y el saldo suma TODAS las filas."},"cancelledByUserId":{"type":"string","description":"Usuario que canceló el documento."},"cancelReason":{"type":"string","description":"Motivo escrito de la cancelación."},"occurredAt":{"type":"string","format":"date-time","description":"Fecha del hecho según el documento, en UTC: admite retrodatar dentro del mes anterior."},"performedByUserId":{"type":"string","description":"Usuario que ejecutó el movimiento."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."}},"example":{"id":"3a9f1c47-6b02-4d85-9e73-0c5a8f2b6194","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","productId":"b6d0937f-51ac-4e28-9370-4f8a1c62d5be","kind":"consumo","quantityMilli":-1000,"enteredQuantityMilli":-1000,"purchaseUnitId":null,"conversionFactorMilli":1000,"unitCostCents":112000,"enteredUnitCostCents":null,"totalCostCents":-112000,"costEstimated":false,"forcedNegative":false,"uncoveredMilli":0,"uncoveredSettledMilli":0,"balanceAfterMilli":7000,"balanceAfterValueCents":784000,"serviceId":"5b8f2d31-6a07-4c92-ae14-73d0b6c85f29","serviceItemId":"e4a71c58-2039-4bd6-8f47-1c9e5b30a726","issueOrigin":"mostrador","returnReason":null,"receiptId":null,"countId":null,"countLineId":null,"supplierReturnId":null,"purchaseOrderRequestId":null,"reasonId":null,"notes":null,"salePriceBeforeCents":null,"salePriceAfterCents":null,"reversesMovementId":null,"reversedMilli":0,"regularizesMovementId":null,"cancelledAt":null,"cancelledByUserId":null,"cancelReason":null,"occurredAt":"2026-08-14T16:02:55.000Z","performedByUserId":"0a3c7e91-5d26-4b80-9f13-8c4e6a2b7d05","createdAt":"2026-08-14T16:02:55.000Z"}},"inventory_receipts":{"type":"object","description":"Las RECEPCIONES de mercancía: la factura del proveedor entrando al almacén. El costo de las líneas es SIN IVA siempre —es lo que valúa las capas y el COGS—; el IVA acreditable del documento viaja aparte en `taxCents` y, junto con el RFC del proveedor, es el insumo de la DIOT. Lo que cada recepción metió al almacén, pieza por pieza, sale en `inventory_movements` filtrando por `receiptId`.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal a la que pertenece la fila. Resuélvela contra el recurso `workshops`."},"supplierId":{"type":"string","description":"Proveedor del documento. Resuélvelo contra el recurso `suppliers`."},"folio":{"type":"integer","description":"Consecutivo del documento dentro de su sucursal: es como el taller lo cita de viva voz."},"series":{"type":"string","description":"Serie del folio (por omisión \"A\"). El par serie + folio es el nombre humano del documento."},"documentRef":{"type":"string","description":"Folio de la factura o remisión del proveedor. Es único por proveedor entre las recepciones vivas: la doble captura de la misma factura es el error más caro del módulo."},"documentDate":{"type":"string","format":"date-time","description":"Fecha del documento del proveedor."},"documentTotalCents":{"type":"integer","description":"Total de la factura tal como lo tecleó el usuario, en centavos. Si no cuadra con las líneas más el IVA, el sistema AVISA, no bloquea."},"taxCents":{"type":"integer","description":"IVA ACREDITABLE del documento, en centavos, fuera del costo de las líneas."},"purchaseOrderId":{"type":"string","description":"Orden de compra contra la que se recibió; nula en la recepción directa, sin pedido previo."},"notes":{"type":"string","description":"Nota libre que capturó el taller en el documento."},"cancelledAt":{"type":"string","format":"date-time","description":"Fecha de cancelación, en UTC. Nula mientras el documento esté vivo: un documento cancelado NO desaparece de esta API, se marca."},"cancelledByUserId":{"type":"string","description":"Usuario que canceló el documento."},"cancelReason":{"type":"string","description":"Motivo escrito de la cancelación."},"receivedAt":{"type":"string","format":"date-time","description":"Cuándo se recibió la mercancía."},"receivedByUserId":{"type":"string","description":"Usuario que recibió la mercancía."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"f19d4c73-0b28-4e61-9a35-6c80d2f5a417","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","supplierId":"8e2c6a04-9f71-4b35-8d60-3a15c7e94b28","folio":318,"series":"A","documentRef":"F-99214","documentDate":"2026-08-11T00:00:00.000Z","documentTotalCents":1299200,"taxCents":179200,"purchaseOrderId":"7d4a2f60-3c95-4e18-b072-8a6f1c53d029","notes":"Llegó completo, sin faltantes.","cancelledAt":null,"cancelledByUserId":null,"cancelReason":null,"receivedAt":"2026-08-11T18:40:12.000Z","receivedByUserId":"0a3c7e91-5d26-4b80-9f13-8c4e6a2b7d05","createdAt":"2026-08-11T18:40:12.000Z","updatedAt":"2026-08-11T18:40:12.000Z"}},"inventory_counts":{"type":"object","description":"Los CONTEOS FÍSICOS: la hoja con la que el taller cuenta el anaquel y cuadra el sistema. Las LÍNEAS del conteo no son recurso de V1 a propósito — lo que el conteo le hizo al inventario ya viaja, asiento por asiento y con su motivo, en `inventory_movements` (`countId` y `countLineId`).","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal a la que pertenece la fila. Resuélvela contra el recurso `workshops`."},"folio":{"type":"integer","description":"Consecutivo del documento dentro de su sucursal: es como el taller lo cita de viva voz."},"series":{"type":"string","description":"Serie del folio (por omisión \"A\"). El par serie + folio es el nombre humano del documento."},"status":{"type":"string","description":"Estado del conteo: `abierta`, `aplicada` o `cancelada`."},"scope":{"type":"string","description":"Alcance del conteo: `todo`, `categoria` o `subcategoria`."},"categoryId":{"type":"string","description":"Categoría que acota el conteo, según su alcance."},"subcategoryId":{"type":"string","description":"Subcategoría que acota el conteo, según su alcance."},"name":{"type":"string","description":"Nombre con el que el taller cita el conteo («Corte de agosto»)."},"notes":{"type":"string","description":"Nota libre que capturó el taller en el documento."},"startedAt":{"type":"string","format":"date-time","description":"Cuándo se abrió el conteo."},"appliedAt":{"type":"string","format":"date-time","description":"Cuándo se aplicaron sus ajustes al inventario."},"cancelledAt":{"type":"string","format":"date-time","description":"Fecha de cancelación, en UTC. Nula mientras el documento esté vivo: un documento cancelado NO desaparece de esta API, se marca."},"createdByUserId":{"type":"string","description":"Usuario que creó la fila."},"appliedByUserId":{"type":"string","description":"Usuario que aplicó el conteo."},"cancelledByUserId":{"type":"string","description":"Usuario que canceló el documento."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"9b1e7d54-2a83-4c06-8f92-5d7a0c3e6b81","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","folio":12,"series":"A","status":"aplicada","scope":"categoria","categoryId":"5a7c1e38-4d92-4067-b85f-2e6a0c3d9147","subcategoryId":null,"name":"Conteo de frenos · agosto","notes":"Se contó con el almacén cerrado, después de las 19:00.","startedAt":"2026-08-01T02:00:00.000Z","appliedAt":"2026-08-01T04:35:22.000Z","cancelledAt":null,"createdByUserId":"0a3c7e91-5d26-4b80-9f13-8c4e6a2b7d05","appliedByUserId":"0a3c7e91-5d26-4b80-9f13-8c4e6a2b7d05","cancelledByUserId":null,"createdAt":"2026-08-01T02:00:00.000Z","updatedAt":"2026-08-01T04:35:22.000Z"}},"purchase_orders":{"type":"object","description":"Las ÓRDENES DE COMPRA al proveedor: lo pedido, antes de que llegue. Sus recepciones se ligan por `inventory_receipts.purchaseOrderId`, y `receivedAt` se sella solo cuando NINGUNA línea quedó pendiente; cancelar una recepción ligada devuelve la orden a `pendiente`.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal a la que pertenece la fila. Resuélvela contra el recurso `workshops`."},"supplierId":{"type":"string","description":"Proveedor del documento. Resuélvelo contra el recurso `suppliers`."},"folio":{"type":"integer","description":"Consecutivo del documento dentro de su sucursal: es como el taller lo cita de viva voz."},"series":{"type":"string","description":"Serie del folio (por omisión \"A\"). El par serie + folio es el nombre humano del documento."},"status":{"type":"string","description":"Estado de la orden: `pendiente`, `parcial`, `recibida` o `cancelada`."},"notes":{"type":"string","description":"Nota libre que capturó el taller en el documento."},"receivedAt":{"type":"string","format":"date-time","description":"Sello de recepción TOTAL: ninguna línea quedó pendiente."},"cancelledAt":{"type":"string","format":"date-time","description":"Fecha de cancelación, en UTC. Nula mientras el documento esté vivo: un documento cancelado NO desaparece de esta API, se marca."},"cancelledByUserId":{"type":"string","description":"Usuario que canceló el documento."},"cancelReason":{"type":"string","description":"Motivo escrito de la cancelación."},"createdByUserId":{"type":"string","description":"Usuario que creó la fila."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"7d4a2f60-3c95-4e18-b072-8a6f1c53d029","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","supplierId":"8e2c6a04-9f71-4b35-8d60-3a15c7e94b28","folio":205,"series":"A","status":"recibida","notes":"Surtir antes del viernes.","receivedAt":"2026-08-11T18:40:12.000Z","cancelledAt":null,"cancelledByUserId":null,"cancelReason":null,"createdByUserId":"0a3c7e91-5d26-4b80-9f13-8c4e6a2b7d05","createdAt":"2026-08-08T16:22:40.000Z","updatedAt":"2026-08-11T18:40:12.000Z"}},"purchase_order_lines":{"type":"object","description":"Las LÍNEAS de una orden de compra: qué producto se pidió, cuánto y a qué costo estimado. Cuelgan de `purchase_orders` por `purchaseOrderId` y no tienen sucursal propia: se consultan con `purchase_order={id}` o `product={id}` (el filtro `workshop` no aplica). Lo que de verdad ENTRÓ al almacén no está aquí sino en `inventory_movements` con `receipt={id}`: la orden es la promesa; la recepción, el hecho.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"purchaseOrderId":{"type":"string","description":"La orden de compra a la que pertenece la línea."},"productId":{"type":"string","description":"Producto del catálogo al que se refiere la fila. Resuélvelo contra el recurso `products`."},"orderedMilli":{"type":"integer","description":"Cantidad PEDIDA en la unidad base del producto, en milésimas (1000 = 1)."},"receivedMilli":{"type":"integer","description":"Cantidad ya RECIBIDA contra esta línea, en milésimas; la diferencia con `orderedMilli` es lo pendiente."},"enteredQuantityMilli":{"type":"integer","description":"Lo que el usuario capturó, en la unidad de compra que eligió, también en milésimas."},"purchaseUnitId":{"type":"string","description":"Unidad de compra elegida al capturar; nula si se capturó en la unidad base."},"conversionFactorMilli":{"type":"integer","description":"Factor CONGELADO de esa unidad a la base, en milésimas (1000 = 1:1)."},"estimatedUnitCostCents":{"type":"integer","description":"Costo unitario ESTIMADO al pedir, en centavos y SIN IVA; nulo si no se capturó. El costo real es el de la recepción (`inventory_movements.unitCostCents`)."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"c1f7a2e4-5b3d-4a9e-8f60-2d7b9c4e1a35","purchaseOrderId":"7d4a2f60-3c95-4e18-b072-8a6f1c53d029","productId":"b6d0937f-51ac-4e28-9370-4f8a1c62d5be","orderedMilli":12000,"receivedMilli":12000,"enteredQuantityMilli":1000,"purchaseUnitId":"e2b6c8d0-1f3a-4b5c-9d7e-8a9b0c1d2e3f","conversionFactorMilli":12000,"estimatedUnitCostCents":8950,"createdAt":"2026-08-08T16:22:40.000Z","updatedAt":"2026-08-11T18:40:12.000Z"}},"purchase_order_requests":{"type":"object","description":"El REPARTO de cada línea de una orden de compra entre las órdenes de servicio que la pidieron (refacción sobre pedido): una fila por línea de OC y por línea de orden, con lo que esa orden pidió y cuánto de eso ya le llegó DIRECTO al recibir, sin pasar por el anaquel. Lo que la línea pidió y no está en ninguna fila de aquí es anaquel (`orderedMilli − Σ requestedMilli`). Cuelga de `purchase_order_lines` por `purchaseOrderLineId` y no tiene sucursal propia: se consulta con `purchase_order_line={id}` o `service={id}` (el filtro `workshop` no aplica). La fila CAMBIA con cada recepción (`issuedDirectMilli` sube) y por eso pagina por `updatedAt`. Los asientos que ese surtido directo produjo están en `inventory_movements` con `purchaseOrderRequestId`.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"purchaseOrderLineId":{"type":"string","description":"La línea de la orden de compra que se reparte. Resuélvela contra `purchase_order_lines`."},"serviceId":{"type":"string","description":"Orden de servicio a la que pertenece la fila. Resuélvela contra el recurso `services`."},"serviceItemId":{"type":"string","description":"La línea de la orden de servicio para la que se pidió la pieza: el producto ya es un renglón de esa orden, con su precio y su aprobación. Resuélvela contra `service_items`."},"requestedMilli":{"type":"integer","description":"Lo que ESA orden pidió de esta línea, en milésimas de la unidad base (1000 = 1)."},"issuedDirectMilli":{"type":"integer","description":"Cuánto de lo pedido ya SALIÓ DIRECTO a la orden al recibir, acumulado entrega tras entrega, en milésimas. Nunca baja: «Pasar a inventario» devuelve la pieza al anaquel por `inventory_movements`, no resta aquí."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"a7c3e912-4f58-4b0d-9e61-2c8d5a7f3b14","purchaseOrderLineId":"c1f7a2e4-5b3d-4a9e-8f60-2d7b9c4e1a35","serviceId":"5b8f2d31-6a07-4c92-ae14-73d0b6c85f29","serviceItemId":"e4a71c58-2039-4bd6-8f47-1c9e5b30a726","requestedMilli":2000,"issuedDirectMilli":2000,"createdAt":"2026-08-08T16:22:40.000Z","updatedAt":"2026-08-11T18:40:12.000Z"}},"supplier_returns":{"type":"object","description":"Las DEVOLUCIONES A PROVEEDOR: la mercancía que se regresa y qué se acordó a cambio. El importe del reembolso es el ACORDADO con el proveedor, no el costo de la capa — la diferencia queda visible, jamás como asiento escondido. Las piezas que salieron están en `inventory_movements` filtrando por `supplierReturnId`.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal a la que pertenece la fila. Resuélvela contra el recurso `workshops`."},"supplierId":{"type":"string","description":"Proveedor del documento. Resuélvelo contra el recurso `suppliers`."},"folio":{"type":"integer","description":"Consecutivo del documento dentro de su sucursal: es como el taller lo cita de viva voz."},"series":{"type":"string","description":"Serie del folio (por omisión \"A\"). El par serie + folio es el nombre humano del documento."},"receiptId":{"type":"string","description":"Recepción de ORIGEN de la mercancía devuelta; opcional. La base impide citar la recepción de otro proveedor."},"reason":{"type":"string","description":"Motivo de la devolución: `defectuosa`, `equivocada`, `sobrante` o `garantia`."},"refundMethod":{"type":"string","description":"Cómo se resarce: `pendiente`, `nota_credito`, `reposicion` o `efectivo`."},"refundAmountCents":{"type":"integer","description":"Importe acordado del reembolso, en centavos. Nace en 0 cuando el método es `pendiente` y se documenta al llegar."},"notes":{"type":"string","description":"Nota libre que capturó el taller en el documento."},"cancelledAt":{"type":"string","format":"date-time","description":"Fecha de cancelación, en UTC. Nula mientras el documento esté vivo: un documento cancelado NO desaparece de esta API, se marca."},"cancelledByUserId":{"type":"string","description":"Usuario que canceló el documento."},"cancelReason":{"type":"string","description":"Motivo escrito de la cancelación."},"createdByUserId":{"type":"string","description":"Usuario que creó la fila."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"4e0c8a35-7f16-4b29-9d54-2c31e6b7f08a","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","supplierId":"8e2c6a04-9f71-4b35-8d60-3a15c7e94b28","folio":7,"series":"A","receiptId":"f19d4c73-0b28-4e61-9a35-6c80d2f5a417","reason":"defectuosa","refundMethod":"nota_credito","refundAmountCents":112000,"notes":"Una pieza con el empaque roto y el disco rayado.","cancelledAt":null,"cancelledByUserId":null,"cancelReason":null,"createdByUserId":"0a3c7e91-5d26-4b80-9f13-8c4e6a2b7d05","createdAt":"2026-08-12T15:18:36.000Z","updatedAt":"2026-08-13T19:04:51.000Z"}},"service_templates":{"type":"object","description":"El CATÁLOGO DE SERVICIOS del taller: las plantillas con las que se arma una orden en dos clics, con su precio, su mano de obra y su garantía por defecto. Todos los importes van SIN IVA. La fila con `workshopId` nulo es la plantilla corporativa que aplica a toda sucursal.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal dueña de la plantilla; NULO = plantilla corporativa, válida en toda la empresa."},"name":{"type":"string","description":"Nombre del servicio como lo lee el cliente."},"description":{"type":"string","description":"Qué incluye el servicio, en palabras para el cliente."},"salePriceCents":{"type":"integer","description":"Precio de venta de la plantilla, en centavos y SIN IVA. El catálogo se guarda neto: si el taller captura con el impuesto dentro, la conversión ocurre al capturar el precio, nunca al cotizar."},"laborHours":{"type":"number","description":"Horas de mano de obra que la plantilla sugiere."},"pricingType":{"type":"string","description":"Cómo precia la plantilla: `fijo` o `por_hora`."},"laborMultiplier":{"type":"number","description":"Multiplicador de la mano de obra sobre la tarifa de la sucursal."},"satProductKey":{"type":"string","description":"Clave de producto/servicio del SAT de la plantilla."},"satUnitKey":{"type":"string","description":"Clave de unidad del SAT de la plantilla."},"warrantyMonths":{"type":"integer","description":"Meses de garantía por defecto del servicio."},"warrantyKm":{"type":"integer","description":"Kilómetros de garantía por defecto del servicio."},"isActive":{"type":"boolean","description":"false = la plantilla ya no se ofrece, pero las órdenes viejas la conservan."},"categoryId":{"type":"string","description":"Categoría del árbol de servicios donde vive la plantilla (obligatoria). Es la categoría de VENTA que heredan sus renglones (`service_items.saleCategoryId`). Resuélvela contra `service_categories`."},"subcategoryId":{"type":"string","description":"Subcategoría de la plantilla (obligatoria), forzosamente de `categoryId`. Resuélvela contra `service_subcategories`."},"bayKind":{"type":"string","description":"Tipo de bahía que suele necesitar este servicio (`alineacion`, `pintura`…): es la sugerencia que la cita y la orden heredan, nunca un candado. Nulo = no se declaró ninguno."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"2c8b4f96-1d73-4a05-9e68-7b3f0c5a9d12","workshopId":null,"categoryId":"7d1e9b42-0c35-4a68-9f27-3b8a6c5d1e04","subcategoryId":"a2f6c815-3e97-4b20-8d41-6c0b9e7f3a58","name":"Afinación mayor 4 cilindros","description":"Bujías, filtros de aire, aceite y gasolina, y limpieza de cuerpo de aceleración.","salePriceCents":285000,"laborHours":2.5,"pricingType":"fijo","laborMultiplier":1,"satProductKey":"78181500","satUnitKey":"E48","warrantyMonths":3,"warrantyKm":5000,"isActive":true,"bayKind":null,"createdAt":"2025-09-19T16:12:38.000Z","updatedAt":"2026-07-02T20:45:13.000Z"}},"service_categories":{"type":"object","description":"El primer nivel del ÁRBOL DE SERVICIOS («01 LUBRICACION», «04 FRENOS», «XX OTROS»…): clasifica las plantillas (`service_templates.categoryId`) y ES la categoría que agrupa la venta (`service_items.saleCategoryId`, `product_subcategories.saleCategoryId`). Toda empresa nace con el árbol de fábrica; «XX OTROS / Otros» existe para que nada se quede sin clasificar. La fila con `brandItemId` la dicta la marca de la red. Solo lectura.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal dueña; NULO = de la empresa, visible en toda sucursal. Con sucursal es «solo esta sucursal»."},"name":{"type":"string","description":"Nombre de la categoría como lo lee el taller (el árbol de fábrica las numera: «04 FRENOS»)."},"color":{"type":"string","description":"Color de la categoría en la pantalla, como token del sistema de diseño (`slate`, `amber`, `rose`…), nunca un hex."},"position":{"type":"integer","description":"Orden en que el taller la acomodó (0 = primera). Es el orden de la pantalla y de los reportes."},"isActive":{"type":"boolean","description":"false = ya no se ofrece al clasificar, pero lo ya clasificado la conserva."},"brandNetworkId":{"type":"string","description":"Red de franquicia cuya MARCA dicta esta fila; NULO = la creó el taller. Con marca, el taller no la renombra ni la retira: solo la ordena u oculta."},"brandItemId":{"type":"string","description":"Id de la fila en el árbol de la marca: el mismo en todas las sucursales de la red, para comparar entre talleres."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"7d1e9b42-0c35-4a68-9f27-3b8a6c5d1e04","workshopId":null,"name":"04 FRENOS","color":"rose","position":3,"isActive":true,"brandNetworkId":null,"brandItemId":null,"createdAt":"2026-09-11T15:02:10.000Z","updatedAt":"2026-09-11T15:02:10.000Z"}},"service_subcategories":{"type":"object","description":"El segundo nivel del ÁRBOL DE SERVICIOS («04 FRENOS → Servicio (Balatas y Zapatas)»). Cuelga de UNA categoría (`categoryId`) y no tiene sucursal propia: el alcance lo dice su categoría, por eso `workshop=` no aplica aquí (400 FILTER_NOT_SUPPORTED) y se consulta con `category={id}`. `service_templates.subcategoryId` y `service_items.saleSubcategoryId` apuntan aquí. Solo lectura.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"categoryId":{"type":"string","description":"Categoría de servicios a la que pertenece. Resuélvela contra `service_categories`."},"name":{"type":"string","description":"Nombre de la subcategoría como lo lee el taller."},"position":{"type":"integer","description":"Orden en que el taller la acomodó (0 = primera). Es el orden de la pantalla y de los reportes."},"isActive":{"type":"boolean","description":"false = ya no se ofrece al clasificar, pero lo ya clasificado la conserva."},"brandNetworkId":{"type":"string","description":"Red de franquicia cuya MARCA dicta esta fila; NULO = la creó el taller. Con marca, el taller no la renombra ni la retira: solo la ordena u oculta."},"brandItemId":{"type":"string","description":"Id de la fila en el árbol de la marca: el mismo en todas las sucursales de la red, para comparar entre talleres."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"a2f6c815-3e97-4b20-8d41-6c0b9e7f3a58","categoryId":"7d1e9b42-0c35-4a68-9f27-3b8a6c5d1e04","name":"Servicio (Balatas y Zapatas)","position":0,"isActive":true,"brandNetworkId":null,"brandItemId":null,"createdAt":"2026-09-11T15:02:10.000Z","updatedAt":"2026-09-11T15:02:10.000Z"}},"suppliers":{"type":"object","description":"Los PROVEEDORES del taller, con su RFC —el que la DIOT necesita junto al IVA de cada recepción— y sus días de crédito. La fila con `workshopId` nulo es el proveedor corporativo, válido en toda sucursal.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal dueña del proveedor; NULO = proveedor corporativo de toda la empresa."},"name":{"type":"string","description":"Nombre comercial del proveedor."},"taxId":{"type":"string","description":"RFC del proveedor: con el IVA de cada recepción es el insumo de la DIOT."},"legalName":{"type":"string","description":"Razón social del proveedor."},"email":{"type":"string","description":"Correo del proveedor."},"phone":{"type":"string","description":"Teléfono del proveedor."},"mobile":{"type":"string","description":"Móvil del proveedor."},"addressLine":{"type":"string","description":"Calle y número."},"addressNeighborhood":{"type":"string","description":"Colonia."},"addressCity":{"type":"string","description":"Municipio o ciudad."},"addressState":{"type":"string","description":"Estado."},"addressPostalCode":{"type":"string","description":"Código postal."},"creditDays":{"type":"integer","description":"Días de crédito que el proveedor le da al taller."},"notes":{"type":"string","description":"Nota libre que capturó el taller en el documento."},"isActive":{"type":"boolean","description":"false = ya no se le compra, pero su historia se conserva."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"8e2c6a04-9f71-4b35-8d60-3a15c7e94b28","workshopId":null,"name":"Refaccionaria del Norte","taxId":"RNO980412K73","legalName":"Refaccionaria del Norte SA de CV","email":"ventas@refanorte.example.mx","phone":"+528183120044","mobile":"+528111223344","addressLine":"Av. Colón 1450","addressNeighborhood":"Industrial","addressCity":"Monterrey","addressState":"Nuevo León","addressPostalCode":"64440","creditDays":30,"notes":"Entrega dos veces al día en zona Cumbres.","isActive":true,"createdAt":"2025-05-27T15:41:07.000Z","updatedAt":"2026-06-18T22:09:33.000Z"}},"service_payments":{"type":"object","description":"EL LIBRO DE COBROS de la orden: cada vez que entró dinero, con su forma de pago y su destino. Append-only —pagina por `createdAt`—: un cobro no se edita ni se borra, se REVERSA con otra fila que apunta a él (`reversesPaymentId`). El desglose fiscal de qué factura saldó cada peso está en `payment_applications`.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal a la que pertenece la fila. Resuélvela contra el recurso `workshops`."},"serviceId":{"type":"string","description":"Orden de servicio a la que pertenece la fila. Resuélvela contra el recurso `services`."},"coversDocumentId":{"type":"string","description":"Comprobante fiscal que este cobro salda, cuando se cobró contra una factura ya emitida."},"method":{"type":"string","description":"Forma de pago: `efectivo`, `tarjeta`, `transferencia`, `cheque` o `otro`."},"amountCents":{"type":"integer","description":"Importe cobrado, en centavos. En una reversa es negativo."},"satPaymentForm":{"type":"string","description":"Clave de forma de pago del SAT (01 efectivo, 03 transferencia, 04 tarjeta…)."},"reference":{"type":"string","description":"Referencia del cobro: autorización de la terminal, folio de la transferencia…"},"notes":{"type":"string","description":"Nota libre del cobro."},"cashSessionId":{"type":"string","description":"Corte de caja (sesión) en el que cayó la operación. Resuélvelo contra `cash_sessions`."},"bankAccountId":{"type":"string","description":"Cuenta bancaria del propio taller involucrada en la operación."},"reversesPaymentId":{"type":"string","description":"Cobro que esta fila reversa. Nulo en un cobro normal."},"reversalReason":{"type":"string","description":"Motivo escrito de la reversa."},"receivedAt":{"type":"string","format":"date-time","description":"Cuándo se recibió el dinero."},"receivedByUserId":{"type":"string","description":"Usuario que recibió el cobro."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."}},"example":{"id":"6c2f8e05-4a91-437d-b168-0d9a5c3e7f42","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","serviceId":"5b8f2d31-6a07-4c92-ae14-73d0b6c85f29","coversDocumentId":null,"method":"tarjeta","amountCents":860720,"satPaymentForm":"04","reference":"AUTH 448210","notes":null,"cashSessionId":"ae13c749-08b6-4d52-9371-2f5c8a0e6b34","bankAccountId":"d83b6f14-5c07-4e92-8a36-1b70e2c9d548","reversesPaymentId":null,"reversalReason":null,"receivedAt":"2026-08-15T21:12:44.000Z","receivedByUserId":"0a3c7e91-5d26-4b80-9f13-8c4e6a2b7d05","createdAt":"2026-08-15T21:12:44.000Z"}},"payment_applications":{"type":"object","description":"EL COMPLEMENTO DE PAGO desglosado: qué pago saldó qué factura, con la base gravable, el impuesto y el saldo antes y después de cada aplicación. Es lo que la contabilidad no puede deducir de `service_payments` sola, y el insumo del CFDI de pagos (REP).","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal a la que pertenece la fila. Resuélvela contra el recurso `workshops`."},"paymentId":{"type":"string","description":"Cobro que se está aplicando. Resuélvelo contra `service_payments`."},"serviceId":{"type":"string","description":"Orden de servicio a la que pertenece la fila. Resuélvela contra el recurso `services`."},"invoiceDocumentId":{"type":"string","description":"Factura que este renglón salda. Resuélvela contra `fiscal_documents`."},"repDocumentId":{"type":"string","description":"CFDI de pagos (REP) que amparó esta aplicación, una vez timbrado."},"amountCents":{"type":"integer","description":"Importe aplicado a esa factura, en centavos."},"installment":{"type":"integer","description":"Número de parcialidad: 1 el primer abono, 2 el segundo…"},"previousBalanceCents":{"type":"integer","description":"Saldo de la factura ANTES de esta aplicación, en centavos."},"outstandingCents":{"type":"integer","description":"Saldo de la factura DESPUÉS de esta aplicación, en centavos."},"taxBaseCents":{"type":"integer","description":"Base gravable del importe aplicado, en centavos."},"taxCents":{"type":"integer","description":"Impuesto trasladado del importe aplicado, en centavos."},"taxRate":{"type":"number","description":"Tasa de impuesto de la aplicación (0.16 = 16 %)."},"objectImpDr":{"type":"string","description":"Clave del SAT «objeto de impuesto» del documento relacionado (01 no objeto, 02 sí objeto…)."},"paidAt":{"type":"string","format":"date-time","description":"Fecha del pago que se está aplicando."},"satPaymentForm":{"type":"string","description":"Clave de forma de pago del SAT con la que se timbra la aplicación."},"currency":{"type":"string","description":"Moneda del pago (ISO 4217)."},"exchangeRate":{"type":"number","description":"Tipo de cambio del pago cuando la moneda no es la de la factura."},"operationNumber":{"type":"string","description":"Número de operación bancaria del pago."},"receiverBankTaxId":{"type":"string","description":"RFC del banco receptor: el del propio taller."},"receiverAccount":{"type":"string","description":"Cuenta del taller donde cayó el pago."},"repRequired":{"type":"boolean","description":"true = esta aplicación EXIGE timbrar un CFDI de pagos."},"repDueAt":{"type":"string","format":"date-time","description":"Fecha límite para timbrar ese CFDI de pagos."},"cancelledAt":{"type":"string","format":"date-time","description":"Cuándo se canceló la aplicación."},"cancelledReason":{"type":"string","description":"Motivo de la cancelación de la aplicación."},"createdByUserId":{"type":"string","description":"Usuario que creó la fila."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."},"payerBankTaxId":{"type":"string","description":"RFC del banco ORDENANTE: el del cliente que pagó. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"payerBankName":{"type":"string","description":"Nombre del banco ordenante del cliente. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"payerAccount":{"type":"string","description":"Cuenta del cliente desde la que salió el pago. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true}},"example":{"id":"2f7b0c96-8d34-4a15-9e28-6c0f3a51b7d4","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","paymentId":"6c2f8e05-4a91-437d-b168-0d9a5c3e7f42","serviceId":"5b8f2d31-6a07-4c92-ae14-73d0b6c85f29","invoiceDocumentId":"b40e9c27-1a58-4d63-8072-5f9c3e6a1408","repDocumentId":null,"amountCents":860720,"installment":1,"previousBalanceCents":860720,"outstandingCents":0,"taxBaseCents":742000,"taxCents":118720,"taxRate":0.16,"objectImpDr":"02","paidAt":"2026-08-15T21:12:44.000Z","satPaymentForm":"04","currency":"MXN","exchangeRate":1,"operationNumber":"448210","receiverBankTaxId":"BBA830831LJ2","receiverAccount":"0182","repRequired":true,"repDueAt":"2026-09-05T05:59:59.000Z","cancelledAt":null,"cancelledReason":null,"createdByUserId":"0a3c7e91-5d26-4b80-9f13-8c4e6a2b7d05","createdAt":"2026-08-15T21:12:45.000Z","updatedAt":"2026-08-15T21:12:45.000Z","payerBankTaxId":"BNM840515VB1","payerBankName":"Banco Nacional de México","payerAccount":"7734"}},"opening_receivables":{"type":"object","description":"La CARTERA IMPORTADA del arranque: lo que los clientes ya le debían al taller cuando entró a Pitflo. Sin ella, la cobranza que ve tu sistema empieza el día de la migración y la contabilidad no cuadra. El saldo vivo es `totalCents − collectedCents`; los abonos en efectivo se ven en `cash_movements.openingReceivableId`.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal a la que pertenece la fila. Resuélvela contra el recurso `workshops`."},"customerId":{"type":"string","description":"Cliente al que pertenece la fila. Resuélvelo contra el recurso `customers`."},"reference":{"type":"string","description":"Folio o referencia del documento de ORIGEN, en el sistema anterior. Es la llave natural del import: volver a subir el archivo no duplica deudas."},"issuedOn":{"type":"string","format":"date-time","description":"Fecha del documento original."},"dueOn":{"type":"string","format":"date-time","description":"Fecha de vencimiento de la deuda."},"totalCents":{"type":"integer","description":"Lo que el documento de origen decía, en centavos."},"collectedCents":{"type":"integer","description":"Lo ya cobrado de esa deuda, en centavos. Solo sube, y admite abonos parciales."},"importBatchId":{"type":"string","description":"Lote de importación que trajo la fila: sirve para agrupar una migración entera."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"c58a2e70-9147-4b03-8d65-3f0c7a91e2b6","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","customerId":"9d4b1e77-2c85-4a13-8f60-5b2e7a9c0d41","reference":"FAC-2025-4471","issuedOn":"2025-12-18T00:00:00.000Z","dueOn":"2026-01-17T00:00:00.000Z","totalCents":1200000,"collectedCents":400000,"importBatchId":"1e4d7b90-3a26-4c58-b071-9f6c2e8a5d13","createdAt":"2026-01-05T18:00:11.000Z","updatedAt":"2026-07-22T17:45:38.000Z"}},"cash_sessions":{"type":"object","description":"LOS CORTES DE CAJA: una sesión por turno de caja registradora, del fondo inicial al arqueo. Mientras la sesión está abierta, todo el sellado del cierre es nulo — no existe el corte a medias. `differenceCents` es `countedCashCents − expectedCashCents` y NO se puede «ajustar» para que cuadre: un faltante se explica, no se borra.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal a la que pertenece la fila. Resuélvela contra el recurso `workshops`."},"cashRegisterId":{"type":"string","description":"Caja registradora física de la sucursal a la que pertenece el turno."},"folio":{"type":"integer","description":"Consecutivo del documento dentro de su sucursal: es como el taller lo cita de viva voz."},"openingFloatCents":{"type":"integer","description":"Fondo con el que abrió la caja, en centavos."},"openedAt":{"type":"string","format":"date-time","description":"Cuándo se abrió la caja."},"openedByUserId":{"type":"string","description":"Usuario que abrió la caja."},"openingNotes":{"type":"string","description":"Nota de la apertura."},"closedAt":{"type":"string","format":"date-time","description":"Cuándo se cerró la caja; nulo mientras el turno siga abierto."},"closedByUserId":{"type":"string","description":"Usuario que cerró la caja."},"expectedCashCents":{"type":"integer","description":"Efectivo que el sistema afirmaba tener al cerrar, en centavos: la suma de los movimientos de efectivo del turno."},"countedCashCents":{"type":"integer","description":"Efectivo contado físicamente en el arqueo, en centavos."},"differenceCents":{"type":"integer","description":"Contado menos esperado, en centavos: negativo es faltante, positivo sobrante."},"differenceReason":{"type":"string","description":"Explicación de la diferencia. Obligatoria a partir de $1.00 en valor absoluto."},"closingNotes":{"type":"string","description":"Nota del cierre."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"ae13c749-08b6-4d52-9371-2f5c8a0e6b34","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","cashRegisterId":"e70c4a18-6b93-4d25-8f01-2a5c9e3b7d64","folio":412,"openingFloatCents":150000,"openedAt":"2026-08-15T13:55:02.000Z","openedByUserId":"0a3c7e91-5d26-4b80-9f13-8c4e6a2b7d05","openingNotes":"Fondo verificado con la gerente.","closedAt":"2026-08-16T01:18:47.000Z","closedByUserId":"0a3c7e91-5d26-4b80-9f13-8c4e6a2b7d05","expectedCashCents":984300,"countedCashCents":984200,"differenceCents":-100,"differenceReason":"Redondeo de un cobro en efectivo.","closingNotes":"Depósito programado para mañana temprano.","createdAt":"2026-08-15T13:55:02.000Z","updatedAt":"2026-08-16T01:18:47.000Z"}},"cash_movements":{"type":"object","description":"EL LIBRO DE LA CAJA: cada peso que entró o salió del cajón, atado a su turno. El importe va SIGNADO según el `kind`, así que la suma del turno ES el efectivo esperado, sin casos especiales. Append-only —pagina por `createdAt`—: un movimiento no se edita, se CONTRA-ASIENTA con otra fila (`reversesMovementId`). Cada movimiento cita EXACTAMENTE UN origen, o ninguno cuando es un retiro o un ingreso manual.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal a la que pertenece la fila. Resuélvela contra el recurso `workshops`."},"cashSessionId":{"type":"string","description":"Corte de caja (sesión) en el que cayó la operación. Resuélvelo contra `cash_sessions`."},"kind":{"type":"string","description":"Qué clase de movimiento es: `fondo_inicial`, `cobro`, `pago_gasto`, `devolucion`, `retiro`, `ingreso` o `reverso_devolucion`."},"amountCents":{"type":"integer","description":"Importe SIGNADO del movimiento, en centavos, atado al `kind`: la suma del turno es el efectivo esperado."},"servicePaymentId":{"type":"string","description":"Cobro de orden que originó el movimiento."},"expensePaymentId":{"type":"string","description":"Pago de gasto que originó el movimiento."},"fiscalDocumentId":{"type":"string","description":"Nota de crédito cuyo dinero se le regresó al cliente."},"openingReceivableId":{"type":"string","description":"Saldo inicial importado que este cobro abona. Admite abonos parciales: una deuda se paga en varias visitas."},"reversesMovementId":{"type":"string","description":"Movimiento que esta fila contra-asienta."},"reversedAt":{"type":"string","format":"date-time","description":"Sello en el movimiento ORIGINAL cuando alguien lo contra-asentó."},"reason":{"type":"string","description":"Motivo de catálogo, obligatorio en `retiro` e `ingreso`: `deposito_bancario`, `compra_menor`, `prestamo`, `otro`…"},"concept":{"type":"string","description":"Concepto escrito del retiro o del ingreso."},"depositReference":{"type":"string","description":"Folio de la ficha de depósito bancario del retiro."},"bankAccountId":{"type":"string","description":"Cuenta bancaria del propio taller involucrada en la operación."},"occurredAt":{"type":"string","format":"date-time","description":"Fecha del hecho según el documento, en UTC. Puede ser anterior a `createdAt` cuando se capturó después."},"createdByUserId":{"type":"string","description":"Usuario que registró el movimiento."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."}},"example":{"id":"9e5b1a34-7c08-4f26-8b93-0d61c2a7e548","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","cashSessionId":"ae13c749-08b6-4d52-9371-2f5c8a0e6b34","kind":"retiro","amountCents":-800000,"servicePaymentId":null,"expensePaymentId":null,"fiscalDocumentId":null,"openingReceivableId":null,"reversesMovementId":null,"reversedAt":null,"reason":"deposito_bancario","concept":"Depósito del corte de la tarde.","depositReference":"DEP-77120","bankAccountId":"d83b6f14-5c07-4e92-8a36-1b70e2c9d548","occurredAt":"2026-08-16T01:05:30.000Z","createdByUserId":"0a3c7e91-5d26-4b80-9f13-8c4e6a2b7d05","createdAt":"2026-08-16T01:05:30.000Z"}},"fiscal_documents":{"type":"object","description":"EL COMPROBANTE FISCAL (CFDI) del taller: facturas, notas de crédito, facturas globales y comprobantes de pago, con el emisor y el receptor CONGELADOS tal como se timbraron. El XML y el PDF no viajan por esta API —se descargan desde la pantalla—; lo que viaja son los datos del negocio. Los saldos (`collectedCents`, `outstandingCents`) se mueven con cada `payment_applications`.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal a la que pertenece la fila. Resuélvela contra el recurso `workshops`."},"serviceId":{"type":"string","description":"Orden de servicio a la que pertenece la fila. Resuélvela contra el recurso `services`."},"customerId":{"type":"string","description":"Cliente al que pertenece la fila. Resuélvelo contra el recurso `customers`."},"type":{"type":"string","description":"Tipo de comprobante: `ingreso` (factura), `egreso` (nota de crédito), `pago` (REP) o `global`."},"status":{"type":"string","description":"Estado del comprobante: `borrador`, `timbrado`, `cancelado` o `error`."},"series":{"type":"string","description":"Serie fiscal del comprobante."},"folio":{"type":"integer","description":"Folio fiscal consecutivo dentro de su serie."},"issuedAt":{"type":"string","format":"date-time","description":"Fecha de emisión del comprobante."},"uuid":{"type":"string","description":"Folio fiscal (UUID) que asignó el SAT al timbrar. Nulo mientras sea borrador."},"stampedAt":{"type":"string","format":"date-time","description":"Cuándo se timbró."},"cancelableUntil":{"type":"string","format":"date-time","description":"Hasta cuándo se puede cancelar sin aceptación del receptor."},"verificationUrl":{"type":"string","description":"URL de verificación del SAT del comprobante."},"issuerLegalName":{"type":"string","description":"Razón social del EMISOR, congelada al timbrar."},"issuerTaxId":{"type":"string","description":"RFC del emisor."},"issuerTaxRegime":{"type":"string","description":"Régimen fiscal del emisor."},"issuerZipCode":{"type":"string","description":"Código postal del lugar de expedición."},"issuerPersonType":{"type":"string","description":"Persona `fisica` o `moral` del emisor."},"currency":{"type":"string","description":"Moneda del comprobante (ISO 4217)."},"exchangeRate":{"type":"number","description":"Tipo de cambio cuando la moneda no es la nacional."},"paymentMethod":{"type":"string","description":"Método de pago del SAT: `PUE` (una exhibición) o `PPD` (parcialidades o diferido)."},"paymentForm":{"type":"string","description":"Clave de forma de pago del SAT (01 efectivo, 03 transferencia…)."},"paymentTerms":{"type":"string","description":"Condición de pago del comprobante: `contado` o `credito`."},"creditDueDate":{"type":"string","format":"date-time","description":"Vencimiento del crédito del comprobante."},"grossSubtotalCents":{"type":"integer","description":"Subtotal antes de descuentos e impuestos, en centavos."},"discountCents":{"type":"integer","description":"Descuento del comprobante, en centavos."},"taxTransferredCents":{"type":"integer","description":"Impuestos TRASLADADOS (IVA cobrado), en centavos."},"taxWithheldCents":{"type":"integer","description":"Impuestos RETENIDOS, en centavos."},"totalCents":{"type":"integer","description":"Total del comprobante, en centavos."},"creditedCents":{"type":"integer","description":"Cuánto de este comprobante se acreditó con notas de crédito, en centavos."},"collectedCents":{"type":"integer","description":"Cuánto se ha cobrado del comprobante, en centavos."},"outstandingCents":{"type":"integer","description":"Saldo pendiente del comprobante, en centavos."},"refundableCents":{"type":"integer","description":"Cuánto se le puede devolver al cliente, en centavos."},"replacesDocumentId":{"type":"string","description":"Comprobante que este sustituye tras una cancelación."},"satRelationType":{"type":"string","description":"Tipo de relación del SAT con el comprobante citado (01 nota de crédito, 04 sustitución…)."},"supersededAt":{"type":"string","format":"date-time","description":"Cuándo este comprobante quedó sustituido por otro."},"creditsDocumentId":{"type":"string","description":"Factura a la que esta nota de crédito le resta."},"creditNoteKind":{"type":"string","description":"Clase de nota de crédito: `descuento`, `devolucion` o `cancelacion`."},"creditReason":{"type":"string","description":"Motivo escrito de la nota de crédito."},"globalPeriodicity":{"type":"string","description":"Periodicidad de la factura global: `dia`, `semana`, `quincena` o `mes`."},"globalPeriodStart":{"type":"string","format":"date-time","description":"Inicio del período que ampara la factura global."},"globalPeriodEnd":{"type":"string","format":"date-time","description":"Fin del período que ampara la factura global."},"coversRemissions":{"type":"boolean","description":"Ids de las remisiones que la factura global ampara."},"cancellationStatus":{"type":"string","description":"Estado de la cancelación ante el SAT: `solicitada`, `aceptada`, `rechazada` o `en_proceso`."},"cancelMotive":{"type":"string","description":"Clave de motivo de cancelación del SAT (01 a 04)."},"cancelledAt":{"type":"string","format":"date-time","description":"Fecha de cancelación, en UTC. Nula mientras el documento esté vivo: un documento cancelado NO desaparece de esta API, se marca."},"createdByUserId":{"type":"string","description":"Usuario que creó la fila."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."},"receiverLegalName":{"type":"string","description":"Razón social del RECEPTOR, congelada al timbrar. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"receiverTaxId":{"type":"string","description":"RFC del receptor. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"receiverTaxRegime":{"type":"string","description":"Régimen fiscal del receptor. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"receiverZipCode":{"type":"string","description":"Código postal fiscal del receptor. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"receiverCfdiUse":{"type":"string","description":"Uso del CFDI que declaró el receptor (G03 gastos en general…). Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"receiverEmail":{"type":"string","description":"Correo al que se le envió el comprobante al receptor. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true}},"example":{"id":"b40e9c27-1a58-4d63-8072-5f9c3e6a1408","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","serviceId":"5b8f2d31-6a07-4c92-ae14-73d0b6c85f29","customerId":"9d4b1e77-2c85-4a13-8f60-5b2e7a9c0d41","type":"ingreso","status":"timbrado","series":"F","folio":1907,"issuedAt":"2026-08-15T21:30:02.000Z","uuid":"A7F3C2E1-58B4-4D09-9E62-0C1A7B35D846","stampedAt":"2026-08-15T21:30:07.000Z","cancelableUntil":"2026-08-16T21:30:07.000Z","verificationUrl":"https://verificacfdi.facturaelectronica.sat.gob.mx/default.aspx?id=A7F3C2E1-58B4-4D09-9E62-0C1A7B35D846","issuerLegalName":"Servicio Automotriz Cumbres SA de CV","issuerTaxId":"SAC120315M42","issuerTaxRegime":"601","issuerZipCode":"64610","issuerPersonType":"moral","currency":"MXN","exchangeRate":1,"paymentMethod":"PUE","paymentForm":"04","paymentTerms":"contado","creditDueDate":null,"grossSubtotalCents":742000,"discountCents":0,"taxTransferredCents":118720,"taxWithheldCents":0,"totalCents":860720,"creditedCents":0,"collectedCents":860720,"outstandingCents":0,"refundableCents":0,"replacesDocumentId":null,"satRelationType":null,"supersededAt":null,"creditsDocumentId":null,"creditNoteKind":null,"creditReason":null,"globalPeriodicity":null,"globalPeriodStart":null,"globalPeriodEnd":null,"coversRemissions":[],"cancellationStatus":null,"cancelMotive":null,"cancelledAt":null,"createdByUserId":"0a3c7e91-5d26-4b80-9f13-8c4e6a2b7d05","createdAt":"2026-08-15T21:29:48.000Z","updatedAt":"2026-08-15T21:30:07.000Z","receiverLegalName":"JOSE RAMON VILLARREAL","receiverTaxId":"VIRJ850612QX3","receiverTaxRegime":"612","receiverZipCode":"66220","receiverCfdiUse":"G03","receiverEmail":"jr.villarreal@example.mx"}},"expenses":{"type":"object","description":"LOS GASTOS del taller: lo que se debe y por qué. La fila con `workshopId` nulo es el gasto corporativo, que aplica a toda sucursal. Un gasto no se borra: se CANCELA, y la cancelación viaja. Lo que efectivamente salió de la caja o del banco está en `expense_payments`.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal a la que se carga el gasto; NULO = gasto corporativo de toda la empresa."},"concept":{"type":"string","description":"Concepto del gasto, en palabras del taller."},"categoryId":{"type":"string","description":"Categoría contable del gasto."},"supplierId":{"type":"string","description":"Proveedor a quien se le debe el gasto."},"amountCents":{"type":"integer","description":"Importe TOTAL del gasto, en centavos, con el IVA dentro."},"taxCents":{"type":"integer","description":"IVA acreditable contenido en el importe, en centavos: con el RFC del proveedor es el insumo de la DIOT."},"incurredAt":{"type":"string","format":"date-time","description":"Fecha en la que se incurrió el gasto."},"dueDate":{"type":"string","format":"date-time","description":"Fecha en la que hay que pagarlo."},"supplierDocument":{"type":"string","description":"Folio del comprobante del proveedor."},"notes":{"type":"string","description":"Nota libre que capturó el taller en el documento."},"settledAt":{"type":"string","format":"date-time","description":"Cuándo quedó saldado el gasto por completo; nulo mientras deba algo."},"receiptId":{"type":"string","description":"Recepción de mercancía que generó el gasto, si nació de una compra a almacén."},"expenseScheduleId":{"type":"string","description":"Plantilla de gasto RECURRENTE de la que nació esta fila: distingue la renta generada de la capturada a mano."},"scheduleDueOn":{"type":"string","format":"date-time","description":"Período que ampara el gasto recurrente."},"cancelledAt":{"type":"string","format":"date-time","description":"Fecha de cancelación, en UTC. Nula mientras el documento esté vivo: un documento cancelado NO desaparece de esta API, se marca."},"cancelledByUserId":{"type":"string","description":"Usuario que canceló el documento."},"cancellationReason":{"type":"string","description":"Motivo de la cancelación del gasto."},"createdByUserId":{"type":"string","description":"Usuario que creó la fila."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"48f2c7b1-0956-4e30-a8d7-6c15b920e4d7","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","concept":"Renta del local · agosto 2026","categoryId":"61a9d3f5-2c47-4b80-9e15-8d0f3a6c7b29","supplierId":"5c9e0b73-1d48-4a26-8f50-3b7a2c6e9014","amountCents":5800000,"taxCents":800000,"incurredAt":"2026-08-01T00:00:00.000Z","dueDate":"2026-08-05T00:00:00.000Z","supplierDocument":"A-1183","notes":"Incluye mantenimiento del estacionamiento.","settledAt":"2026-08-04T19:22:08.000Z","receiptId":null,"expenseScheduleId":"f30b8d51-4e72-4c96-a015-7d2c6b9e3a80","scheduleDueOn":"2026-08-05T00:00:00.000Z","cancelledAt":null,"cancelledByUserId":null,"cancellationReason":null,"createdByUserId":"0a3c7e91-5d26-4b80-9f13-8c4e6a2b7d05","createdAt":"2026-08-01T14:02:19.000Z","updatedAt":"2026-08-04T19:22:08.000Z"}},"expense_payments":{"type":"object","description":"LOS PAGOS DEL GASTO: la otra mitad del flujo de efectivo. `expenses` dice lo que se debe; esto dice lo que salió, cuándo y por dónde. Append-only —pagina por `createdAt`—: un pago se REVERSA con otra fila, jamás se edita.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal a la que pertenece la fila. Resuélvela contra el recurso `workshops`."},"expenseId":{"type":"string","description":"Gasto que este pago abona. Resuélvelo contra `expenses`."},"method":{"type":"string","description":"Forma de pago: `efectivo`, `tarjeta`, `transferencia`, `cheque` o `otro`."},"amountCents":{"type":"integer","description":"Importe pagado, en centavos. En una reversa es negativo."},"reference":{"type":"string","description":"Referencia del pago: folio de la transferencia, número de cheque…"},"notes":{"type":"string","description":"Nota libre del pago."},"cashSessionId":{"type":"string","description":"Corte de caja (sesión) en el que cayó la operación. Resuélvelo contra `cash_sessions`."},"fromRegister":{"type":"boolean","description":"true = el dinero salió del CAJÓN de la caja registradora, y por eso hay un `cash_movements` gemelo."},"bankAccountId":{"type":"string","description":"Cuenta bancaria del propio taller involucrada en la operación."},"reversesPaymentId":{"type":"string","description":"Pago que esta fila reversa."},"reversalReason":{"type":"string","description":"Motivo escrito de la reversa."},"paidAt":{"type":"string","format":"date-time","description":"Cuándo se pagó."},"paidByUserId":{"type":"string","description":"Usuario que registró el pago."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."}},"example":{"id":"b7c4e903-5a18-4d62-8f30-1e9c5a20d763","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","expenseId":"48f2c7b1-0956-4e30-a8d7-6c15b920e4d7","method":"transferencia","amountCents":5800000,"reference":"SPEI 20260804-118342","notes":null,"cashSessionId":null,"fromRegister":false,"bankAccountId":"d83b6f14-5c07-4e92-8a36-1b70e2c9d548","reversesPaymentId":null,"reversalReason":null,"paidAt":"2026-08-04T19:22:08.000Z","paidByUserId":"0a3c7e91-5d26-4b80-9f13-8c4e6a2b7d05","createdAt":"2026-08-04T19:22:08.000Z"}},"remissions":{"type":"object","description":"LAS REMISIONES: el papel con el que se entrega el trabajo cuando el cliente no pidió factura. Congela al cliente y al vehículo tal como se imprimieron. Si después el ticket se factura —solo o dentro de una global—, `invoiceDocumentId` y `coveredAt` lo dicen.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal a la que pertenece la fila. Resuélvela contra el recurso `workshops`."},"serviceId":{"type":"string","description":"Orden de servicio a la que pertenece la fila. Resuélvela contra el recurso `services`."},"series":{"type":"string","description":"Serie del folio (por omisión \"A\"). El par serie + folio es el nombre humano del documento."},"folio":{"type":"integer","description":"Consecutivo del documento dentro de su sucursal: es como el taller lo cita de viva voz."},"status":{"type":"string","description":"Estado de la remisión: `emitida`, `amparada` (ya facturada) o `cancelada`."},"issuedAt":{"type":"string","format":"date-time","description":"Cuándo se emitió."},"issuerName":{"type":"string","description":"Nombre del taller congelado en el papel."},"issuerAddress":{"type":"string","description":"Domicilio del taller congelado en el papel."},"issuerPhone":{"type":"string","description":"Teléfono del taller congelado en el papel."},"customerId":{"type":"string","description":"Cliente al que pertenece la fila. Resuélvelo contra el recurso `customers`."},"vehicleLabel":{"type":"string","description":"Vehículo tal como se imprimió («Nissan Versa 2021»)."},"odometer":{"type":"integer","description":"Kilometraje impreso en la remisión."},"currency":{"type":"string","description":"Moneda de la remisión."},"grossSubtotalCents":{"type":"integer","description":"Subtotal antes de descuentos e impuestos, en centavos."},"discountCents":{"type":"integer","description":"Descuento, en centavos."},"taxRate":{"type":"number","description":"Tasa de impuesto aplicada."},"taxCents":{"type":"integer","description":"Impuesto, en centavos."},"totalCents":{"type":"integer","description":"Total de la remisión, en centavos."},"paymentCondition":{"type":"string","description":"Condición de pago: `contado` o `credito`."},"creditDueDate":{"type":"string","format":"date-time","description":"Vencimiento del crédito de la remisión."},"customerMessage":{"type":"string","description":"Mensaje al cliente impreso en la remisión."},"invoiceDocumentId":{"type":"string","description":"Comprobante que terminó amparando esta remisión."},"coveredAt":{"type":"string","format":"date-time","description":"Cuándo quedó amparada por una factura (columna `amparada_at`)."},"cancelledAt":{"type":"string","format":"date-time","description":"Fecha de cancelación, en UTC. Nula mientras el documento esté vivo: un documento cancelado NO desaparece de esta API, se marca."},"cancelledByUserId":{"type":"string","description":"Usuario que canceló el documento."},"cancellationReason":{"type":"string","description":"Motivo de la cancelación."},"issuedByUserId":{"type":"string","description":"Usuario que emitió la remisión."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."},"customerName":{"type":"string","description":"Nombre del cliente congelado en el papel. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"customerPhone":{"type":"string","description":"Teléfono del cliente congelado en el papel. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"vehiclePlate":{"type":"string","description":"Placa congelada en el papel. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"vehicleVin":{"type":"string","description":"VIN congelado en el papel. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true}},"example":{"id":"d91f5c04-2b76-4a38-8e50-7c3a1d69b025","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","serviceId":"5b8f2d31-6a07-4c92-ae14-73d0b6c85f29","series":"R","folio":3348,"status":"emitida","issuedAt":"2026-08-15T21:40:19.000Z","issuerName":"Pitflo Cumbres","issuerAddress":"Av. Paseo de los Leones 3120, Cumbres 3er Sector, Monterrey, NL","issuerPhone":"+528183451200","customerId":"9d4b1e77-2c85-4a13-8f60-5b2e7a9c0d41","vehicleLabel":"Nissan Versa 2021 · Gris Oxford","odometer":87450,"currency":"MXN","grossSubtotalCents":742000,"discountCents":0,"taxRate":0.16,"taxCents":118720,"totalCents":860720,"paymentCondition":"contado","creditDueDate":null,"customerMessage":"Se recomienda cambiar balatas traseras en la próxima visita.","invoiceDocumentId":null,"coveredAt":null,"cancelledAt":null,"cancelledByUserId":null,"cancellationReason":null,"issuedByUserId":"0a3c7e91-5d26-4b80-9f13-8c4e6a2b7d05","createdAt":"2026-08-15T21:40:19.000Z","updatedAt":"2026-08-15T21:40:19.000Z","customerName":"José Ramón Villarreal","customerPhone":"+528118765432","vehiclePlate":"SXH-482-C","vehicleVin":"3N1CN8AD5ML830472"}},"fixed_assets":{"type":"object","description":"EL ACTIVO FIJO del taller: la rampa, el escáner, la compresora. La depreciación NO viaja y no se almacena en ninguna parte — se DERIVA al leer (lineal, mensual) con `originalCostCents`, `annualRatePct` e `inUseFrom`, exactamente igual que la pantalla. La fila con `workshopId` nulo es un activo de la empresa, no de una sucursal.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal dueña del activo; NULO = activo de la empresa."},"name":{"type":"string","description":"Nombre del activo."},"assetType":{"type":"string","description":"Tipo de activo del catálogo (`equipo_taller`, `computo`, `mobiliario`, `vehiculo`…), que sugiere la tasa."},"annualRatePct":{"type":"number","description":"Tasa anual de depreciación en porcentaje (10 = 10 % al año). Vale por sí misma, aunque el tipo sugiera otra."},"originalCostCents":{"type":"integer","description":"Monto original de la inversión (MOI), en centavos."},"inUseFrom":{"type":"string","format":"date-time","description":"Desde cuándo está en uso: es la fecha en la que arranca la depreciación."},"disposedOn":{"type":"string","format":"date-time","description":"Fecha de baja del activo; nula mientras siga en uso."},"disposalReason":{"type":"string","description":"Motivo de la baja (venta, robo, obsolescencia…)."},"notes":{"type":"string","description":"Nota libre del activo."},"createdAt":{"type":"string","format":"date-time","description":"Fecha y hora de alta de la fila, en UTC (ISO 8601)."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"6b03e5c9-4a71-4283-9d06-1f5c8a2e7b40","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","name":"Rampa de dos postes 4 toneladas","assetType":"equipo_taller","annualRatePct":10,"originalCostCents":8500000,"inUseFrom":"2025-03-01T00:00:00.000Z","disposedOn":null,"disposalReason":null,"notes":"Bahía 3. Mantenimiento anual en marzo.","createdAt":"2025-03-04T16:48:52.000Z","updatedAt":"2025-03-04T16:48:52.000Z"}},"ledger_account_maps":{"type":"object","description":"EL MAPEO concepto→cuenta del contador: con qué cuenta contable se registra cada concepto del taller. La fila con `workshopId` nulo es el default de la empresa y la de la sucursal lo sobrescribe — pide las dos, o tu póliza saldrá incompleta. No tiene `createdAt`: `updatedAt` es el único sello, y es el que pagina.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"workshopId":{"type":"string","description":"Sucursal con override; NULO = default de la empresa."},"conceptKey":{"type":"string","description":"Concepto del taller (`venta_refaccion`, `iva_trasladado`, `banco:<bankAccountId>`, `gasto:<categoryId>`…). El sufijo tras los dos puntos apunta a la entidad concreta."},"accountCode":{"type":"string","description":"Código de la cuenta contable a la que se registra el concepto."},"accountName":{"type":"string","description":"Nombre de esa cuenta, como lo escribió el contador."},"updatedAt":{"type":"string","format":"date-time","description":"Fecha y hora del último cambio, en UTC. Es el campo que filtra `updated_since` y el que ordena la página."}},"example":{"id":"3c8f0b64-5d19-4a27-8e05-9b1a6c37e2d4","workshopId":null,"conceptKey":"venta_refaccion","accountCode":"401-01-000","accountName":"Ventas de refacciones","updatedAt":"2026-02-17T18:35:41.000Z"}},"employees":{"type":"object","description":"EL PADRÓN DE PERSONAS de tu empresa, en vista MÍNIMA: quién es, qué rol tiene, en qué estado está y a qué sucursales alcanza. Existe para que puedas resolver a un nombre los `…ByUserId` que esta API devuelve por todos lados (quién recibió el pago, quién movió la refacción, quién cerró la caja); sin él, cada actor es un uuid ciego. NO trae credenciales ni su reloj (contraseña, intentos fallidos, bloqueos), ni sesiones, ni preferencias personales (tema, zona horaria, idioma, avisos), ni la última conexión: eso es la cerradura de la cuenta, no dato del negocio. Una persona dada de baja deja de aparecer el mismo día.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"roleId":{"type":"string","description":"Rol que decide QUÉ puede hacer la persona. Es un id de rol de tu propia empresa (los roles son configurables por taller), así que su nombre se administra dentro de Pitflo y aquí solo viaja la llave: dos personas con el mismo `roleId` tienen exactamente los mismos permisos."},"status":{"type":"string","description":"Estado de la cuenta: `pending_activation` (invitada, todavía no entra), `active`, `suspended` (bloqueada temporalmente) o `disabled` (dada de baja del sistema, conserva su historial)."},"workshopIds":{"type":"array","items":{"type":"string"},"description":"Las sucursales a las que la persona alcanza HOY, resueltas en cada consulta desde sus accesos VIVOS: un acceso revocado desaparece de esta lista al instante. Arreglo vacío = todavía no le han dado ninguna. Resuélvelas contra el recurso `workshops`."},"createdAt":{"type":"string","format":"date-time","description":"Cuándo se dio de alta la persona en el sistema, en UTC."},"updatedAt":{"type":"string","format":"date-time","description":"Último cambio de su ficha, en UTC: es el sello por el que este recurso pagina y el que responde a `updated_since`."},"name":{"type":"string","description":"Nombre de la persona, tal como lo firma en el taller. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true},"email":{"type":"string","description":"Correo con el que la persona entra a Pitflo. Cuando el taller le dio una cuenta SIN buzón propio, es un correo técnico del dominio `no-mail.pitflo.local`: sirve como identificador, no como buzón — no le escribas ahí. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true}},"example":{"id":"9c4b7e21-5d38-4a60-8f19-2b7e6c05d34a","name":"María Fernanda Robles","email":"mrobles@tallercumbres.mx","roleId":"b8d1f4a7-2c39-4e05-9a68-7f3b1d24c6e0","status":"active","workshopIds":["3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73"],"createdAt":"2026-01-14T16:22:08.000Z","updatedAt":"2026-08-11T19:03:44.000Z"}},"audit_log":{"type":"object","description":"LA BITÁCORA de tu empresa: qué pasó, cuándo, quién lo hizo y sobre qué. Es el libro con el que un auditor reconstruye una jornada sin pedirle capturas de pantalla a nadie, y el mismo que alimenta la pantalla de Auditoría. Cuatro datos y ni uno más: el DETALLE de cada hecho (`metadata`) NO se sirve por decisión expresa —es un campo libre donde cabe cualquier cosa, incluido dato personal de un tercero, y servirlo sería un agujero que nadie vuelve a revisar—. Sus filas son INMUTABLES: se escriben una vez y jamás se editan ni se borran, así que paginar por el hecho no te hace perder nada.","properties":{"id":{"type":"string","description":"Identificador único e inmutable de la fila (UUID). Es la llave con la que deduplicas al sincronizar."},"occurredAt":{"type":"string","format":"date-time","description":"Cuándo ocurrió el hecho, en UTC. Es el sello por el que este recurso ordena y pagina, y el que responde a `updated_since`: como la fila nunca cambia después de escrita, el sello del hecho y el de su última versión son el mismo."},"workshopId":{"type":"string","description":"Sucursal donde ocurrió el hecho, cuando el hecho tiene una. Es NULO para los actos de empresa que no pasan en un taller (dar de alta una sucursal, cambiar la configuración corporativa). Resuélvelo contra el recurso `workshops`."},"actorType":{"type":"string","description":"Qué clase de actor lo hizo: `user` (una persona de tu empresa), `owner` (personal de Pitflo actuando con autorización), `system` (un proceso automático de la casa, como el cierre de jornadas) o `integracion` (una integración empujando datos, como un conector de telemática)."},"action":{"type":"string","description":"Qué se hizo, como llave estable en punto (`servicio.creado`, `pago.registrado`, `usuario.desactivado`). Es el vocabulario interno de Pitflo y crece con el producto: trátalo como texto abierto y no como enumeración cerrada."},"resourceType":{"type":"string","description":"Sobre qué clase de cosa se actuó (`service`, `payment`, `user`…). Igual que `action`, es vocabulario abierto."},"resourceId":{"type":"string","description":"La fila concreta sobre la que se actuó. Es NULO en los hechos que no apuntan a una fila (un inicio de sesión, una exportación)."},"actorId":{"type":"string","description":"Quién lo hizo. Con `actorType = user` es el id de una persona: resuélvelo contra el recurso `employees`. Con `integracion` es el id de la FUENTE, no de una persona. Es NULO cuando el actor es el sistema. Dato personal (marca pii de la allowlist): al marcarlo en tu integración estás sacando datos personales de tus clientes a un sistema externo.","x-pii":true}},"example":{"id":"a1f6c803-9b2e-4d47-85c0-3e9a17d5b268","occurredAt":"2026-08-14T15:41:02.000Z","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","actorType":"user","actorId":"9c4b7e21-5d38-4a60-8f19-2b7e6c05d34a","action":"servicio.etapa_cambiada","resourceType":"service","resourceId":"5b8f2d31-6a07-4c92-ae14-73d0b6c85f29"}},"network_workshops":{"type":"object","description":"LOS TALLERES VINCULADOS a tu red, re-resueltos en cada consulta: un taller que se desvincula desaparece de esta lista al instante. Trae su identidad comercial y su ubicación; la identidad FISCAL del franquiciatario no se le vende a la red.","properties":{"id":{"type":"string","description":"Identificador de la sucursal vinculada. Es el mismo `workshopId` que traen las órdenes de `network_services`."},"name":{"type":"string","description":"Nombre comercial de la sucursal."},"brand":{"type":"string","description":"Marca bajo la que opera la sucursal."},"city":{"type":"string","description":"Municipio o ciudad de la sucursal."},"state":{"type":"string","description":"Estado de la sucursal."},"timezone":{"type":"string","description":"Zona horaria IANA de la sucursal: la que hay que usar para leer las fechas UTC de sus órdenes."},"currency":{"type":"string","description":"Moneda de la sucursal (ISO 4217). Todo importe de la red va en centavos de la moneda de SU sucursal."},"active":{"type":"boolean","description":"true = la sucursal está operando; false = está suspendida."},"organization":{"type":"object","description":"La empresa dueña de la sucursal, con su `id` y su `name`: en una red, cada taller puede ser de un franquiciatario distinto."},"linkedSince":{"type":"string","format":"date-time","description":"Desde cuándo la sucursal está vinculada a tu red."}},"example":{"id":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","name":"Pitflo Cumbres","brand":"Pitflo","city":"Monterrey","state":"Nuevo León","timezone":"America/Monterrey","currency":"MXN","active":true,"organization":{"id":"7a15c3e8-0d92-4b47-8f36-2c908e5b1d74","name":"Grupo Automotriz Cumbres"},"linkedSince":"2026-02-01T00:00:00.000Z"}},"network_services":{"type":"object","description":"LAS ÓRDENES de los talleres vinculados, con las MISMAS columnas que el panel de red: lo aprobado y lo facturado, la etapa y el vehículo en grueso. Nunca el cliente, nunca la placa, nunca el VIN, nunca el costo ni el margen. Cada consulta re-resuelve vínculos y permisos: el taller sin permiso de detalle vigente simplemente NO aparece.","properties":{"id":{"type":"string","description":"Identificador de la orden."},"workshopId":{"type":"string","description":"Sucursal dueña de la orden. La API pagina la unión de la red entera, así que sin este campo no sabrías de qué taller es cada orden."},"folio":{"type":"string","description":"Folio de la orden ya armado como lo cita el taller (serie y consecutivo pegados, p. ej. \"A-2841\")."},"createdAt":{"type":"string","format":"date-time","description":"Cuándo se abrió la orden, en UTC."},"stage":{"type":"string","description":"Llave CANÓNICA de la etapa: es lo que hace comparables dos tableros que nadie nombró igual."},"stageName":{"type":"string","description":"Nombre que ESE taller le puso a su columna."},"quoteStatus":{"type":"string","description":"Estado del presupuesto de la orden."},"decidedAt":{"type":"string","format":"date-time","description":"Cuándo el cliente decidió el presupuesto."},"approvedCents":{"type":"integer","description":"Total APROBADO de la orden, en centavos."},"invoicedCents":{"type":"integer","description":"Total FACTURADO de la orden, en centavos; nulo si todavía no se factura."},"vehicle":{"type":"object","description":"El vehículo en grueso: `make`, `model` y `year`. Sin placa y sin VIN, a propósito."}},"example":{"id":"5b8f2d31-6a07-4c92-ae14-73d0b6c85f29","workshopId":"3f2a9c10-7b41-4e58-9a02-1c6d5e8f4b73","folio":"A-2841","createdAt":"2026-08-14T13:47:09.000Z","stage":"reparacion","stageName":"En reparación","quoteStatus":"aprobado","decidedAt":"2026-08-14T14:05:31.000Z","approvedCents":860720,"invoicedCents":null,"vehicle":{"make":"Nissan","model":"Versa","year":2021}}}},"responses":{"error_400":{"description":"Petición inválida (p. ej. INVALID_CURSOR).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"error_401":{"description":"Sin token, token inválido, revocado o vencido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"error_402":{"description":"CAPABILITY_NOT_IN_PLAN: el plan de la empresa ya no incluye la API. Tras bajar de Empresarial la llave sigue leyendo treinta días; después contesta esto (con `desactivadaEl` y `revocadaEl` en `details`) hasta que la empresa vuelva a Empresarial.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"error_403":{"description":"RESOURCE_NOT_ENABLED: el recurso no está habilitado en tu integración (o una cookie de sesión tocó /v1).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"error_404":{"description":"No existe — o no existe para tu empresa: es el mismo 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"error_409":{"description":"WEBHOOK_DISABLED: el webhook está apagado; reactívalo antes de mandarle un ping.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"error_429":{"description":"Límite de peticiones; respeta Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}}}}