{
  "ok": true,
  "datos": {
    "app": "Jopi Tools",
    "proposito": "Agenda de servicios de lavado por ciudad. Cada técnico tiene tres casillas al día: 9:00 a.m., 12:00 p.m. y 3:00 p.m.",
    "base": "http://127.0.0.1:8787/api",
    "hoy": "2026-09-19",
    "comoEmpezar": [
      "Este documento ya trae todas las operaciones con sus campos y un ejemplo cada una; no hace falta pedir nada más.",
      "Lee siempre «error.arregla» cuando algo falle: dice exactamente qué cambiar para que la siguiente llamada salga.",
      "Si una respuesta trae «avisos», ahí está lo que se interpretó distinto a lo que mandaste. Corrígelo en la siguiente."
    ],
    "autenticacion": {
      "como": "Cabecera «Authorization: Bearer <token>». También vale «X-Jopi-Token: <token>».",
      "donde": "El token está en la pantalla «API» de Jopi Tools, con botón para copiarlo.",
      "nota": "El token nunca va en la URL: quedaría escrito en registros y en el historial.",
      "sinToken": [
        "/",
        "/api",
        "/api/salud",
        "/api/descripcion",
        "/api/ayuda",
        "/api/horarios"
      ]
    },
    "respuesta": {
      "exito": {
        "ok": true,
        "datos": "…lo que devuelve la operación",
        "avisos": "(opcional) qué se interpretó distinto",
        "pista": "(opcional) qué llamar después"
      },
      "error": {
        "ok": false,
        "error": {
          "codigo": "identificador estable del fallo, p. ej. «casilla_ocupada»",
          "mensaje": "qué pasó, en una línea",
          "arregla": "QUÉ HACER para que funcione. Es lo primero que hay que leer.",
          "recibido": "lo que mandaste, ya interpretado",
          "detalle": "datos para reintentar: sugerencias de horario, nombres disponibles, ids que sí existen",
          "comoSeHace": "el manual de esa operación: campos obligatorios, opcionales y un ejemplo completo"
        }
      }
    },
    "reglas": [
      "Un técnico solo puede tener una cita por turno. Si chocas, el 409 trae en «detalle.sugerencias» los huecos libres más cercanos.",
      "Una cita cancelada libera la casilla pero se queda en el historial: para cancelar usa PATCH con estado=cancelada, no DELETE.",
      "Si no indicas técnico al crear una cita, se asigna el primero libre de esa ciudad y turno.",
      "Los tapetes son otra cosa: no tienen técnico ni turno. Se recogen, se lavan en la empresa y se entregan, y avanzan por pasos.",
      "Los campos que no mandes en un PATCH se quedan como estaban.",
      "Las ciudades y los técnicos se pueden nombrar por id o por nombre; los nombres se comparan sin tildes ni mayúsculas.",
      "El dinero de una cita cuenta el día de la cita; el de un tapete, el día que se entrega."
    ],
    "formatos": {
      "fecha": "AAAA-MM-DD, o «hoy», «mañana», «pasado mañana», «ayer», «en 3 días», «18/09/2026».",
      "horario": "Solo tres turnos: 09:00 (9:00 a.m.), 12:00 (12:00 p.m.), 15:00 (3:00 p.m.). Se aceptan «9am», «3 pm», «mañana», «mediodía», «tarde».",
      "estadoDeCita": "pendiente, confirmada, completada, cancelada",
      "estadoDeTapete": "por_recoger, en_lavado, listo, entregado, cancelado — el recorrido normal es por_recoger → en_lavado → listo → entregado.",
      "telefono": "Como venga escrito; se guarda en dígitos. «+57 310 441 0217» y «3104410217» son lo mismo.",
      "valor": "Pesos colombianos sin decimales. Si no lo mandas y el servicio está en el catálogo, se usa el valor sugerido.",
      "ciudadYTecnico": "Por id o por nombre, sin importar tildes ni mayúsculas («cali» encuentra «Cali»)."
    },
    "flujos": [
      {
        "situacion": "El cliente pregunta para cuándo hay cupo y quiere agendar.",
        "pasos": [
          "GET /disponibilidad?ciudad=Cali&desde=hoy&dias=7 — devuelve los huecos con los técnicos libres de cada uno.",
          "Ofrécele dos o tres opciones concretas (día y turno).",
          "POST /citas con ciudad, fecha, horario, servicio, cliente, telefono, direccion, barrio y valor.",
          "Si responde 409, usa una de las «sugerencias» que trae el error y repite."
        ]
      },
      {
        "situacion": "Llama alguien que ya tiene cita y quiere cambiarla.",
        "pasos": [
          "GET /citas?q=<su teléfono> — el teléfono es lo que menos cambia entre lo que dice y lo que está escrito.",
          "POST /citas/<id>/mover con la fecha o el horario nuevos."
        ]
      },
      {
        "situacion": "El cliente cancela.",
        "pasos": [
          "GET /citas?q=<teléfono o nombre> para sacar el id.",
          "PATCH /citas/<id> con {\"estado\": \"cancelada\"} — libera la casilla y deja constancia."
        ]
      },
      {
        "situacion": "Quieren que les recojan unos tapetes.",
        "pasos": [
          "POST /tapetes con ciudad, cliente, telefono, direccion, barrio, descripcion, cantidad, valor y fechaRecoge.",
          "Cuando se recojan, se laven y se entreguen: POST /tapetes/<id>/avanzar en cada paso.",
          "GET /tapetes/tablero para ver qué hay en el taller y qué está atrasado."
        ]
      },
      {
        "situacion": "Preguntan cuánto se ha hecho, quién tiene la plata o cómo va el negocio.",
        "pasos": [
          "GET /ingresos?periodo=mes — cuánto se cobró, cuánto falta por cobrar y el reparto por técnico y por ciudad.",
          "GET /estadisticas?periodo=ultimos90 — lee «hallazgos»: trae las conclusiones ya escritas."
        ]
      },
      {
        "situacion": "Abren plaza en una ciudad nueva.",
        "pasos": [
          "POST /ciudades con el nombre.",
          "POST /tecnicos por cada técnico, con «ciudad» y «nombre».",
          "Desde ese momento la ciudad tiene tres casillas al día por cada técnico."
        ]
      }
    ],
    "sincronizacion": {
      "para": "Lo que usan los otros computadores con Jopi Tools para trabajar sobre esta misma agenda.",
      "version": "GET /api/version — devuelve cuándo se guardó por última vez. Sirve para preguntar «¿cambió algo?» sin traerse nada.",
      "instantanea": "GET /api/instantanea — la base entera: ciudades, técnicos, servicios y citas."
    },
    "operaciones": [
      {
        "llamada": "GET /api/resumen",
        "operacion": "resumen",
        "para": "Contesta los «¿cuántos…?»: ciudades, técnicos, servicios, citas de hoy, casillas libres y dinero del día y del mes.",
        "obligatorios": [],
        "opcionales": [],
        "ejemplo": {},
        "devuelve": "Números generales y el desglose por ciudad.",
        "consejos": [
          "Es la primera llamada si te preguntan por el tamaño del negocio."
        ]
      },
      {
        "llamada": "GET /api/ciudades",
        "operacion": "ciudades.listar",
        "para": "Lista las ciudades que tienen agenda.",
        "obligatorios": [],
        "opcionales": [],
        "ejemplo": {},
        "devuelve": "Cada ciudad con su id, nombre y si está activa."
      },
      {
        "llamada": "POST /api/ciudades",
        "operacion": "ciudades.crear",
        "para": "Abre una agenda en una ciudad nueva.",
        "obligatorios": [
          {
            "campo": "nombre",
            "que": "Cómo se llama la ciudad.",
            "ejemplo": "Palmira"
          }
        ],
        "opcionales": [
          "nota — Apunte interno: qué zona cubre, quién la coordina.",
          "activa — Si se puede agendar en ella. (por defecto: true)"
        ],
        "ejemplo": {
          "nombre": "Palmira",
          "nota": "Zona sur"
        },
        "devuelve": "La ciudad creada, con su id.",
        "consejos": [
          "Una ciudad recién creada no sirve para agendar hasta que tenga técnicos: sigue con POST /tecnicos."
        ]
      },
      {
        "llamada": "PATCH /api/ciudades/:id",
        "operacion": "ciudades.editar",
        "para": "Renombra una ciudad o la activa y desactiva.",
        "obligatorios": [
          {
            "campo": "id",
            "que": "Id de la ciudad. Va en la ruta.",
            "ejemplo": "ciu_mu603u2k1d8"
          }
        ],
        "opcionales": [
          "nombre — Nombre nuevo.",
          "nota — Apunte interno.",
          "activa — Falso para dejar de agendar sin perder el historial."
        ],
        "ejemplo": {
          "activa": false
        },
        "devuelve": "La ciudad con los cambios aplicados.",
        "consejos": [
          "Manda solo los campos que cambian; lo que no envíes se queda como está."
        ]
      },
      {
        "llamada": "DELETE /api/ciudades/:id",
        "operacion": "ciudades.eliminar",
        "para": "Borra una ciudad. Si tiene técnicos o citas, hay que insistir con forzar.",
        "obligatorios": [
          {
            "campo": "id",
            "que": "Id de la ciudad. Va en la ruta.",
            "ejemplo": "ciu_mu603u2k1d8"
          }
        ],
        "opcionales": [
          "forzar — Borra también sus técnicos y todas sus citas. Sin esto, con datos dentro se rechaza. (por defecto: false)"
        ],
        "ejemplo": {
          "forzar": true
        },
        "devuelve": "Qué se borró y cuántos técnicos y citas se llevó por delante.",
        "consejos": [
          "Casi siempre es mejor PATCH con activa=false: deja de agendar y conserva el historial."
        ]
      },
      {
        "llamada": "GET /api/tecnicos",
        "operacion": "tecnicos.listar",
        "para": "Lista los técnicos, de todas las ciudades o de una.",
        "obligatorios": [],
        "opcionales": [
          "ciudad — La ciudad de la agenda. Vale el nombre o el id."
        ],
        "ejemplo": {
          "ciudad": "Cali"
        },
        "devuelve": "Cada técnico con su ciudad, teléfono y si está activo."
      },
      {
        "llamada": "POST /api/tecnicos",
        "operacion": "tecnicos.crear",
        "para": "Agrega un técnico a una ciudad. Cada técnico suma tres casillas al día.",
        "obligatorios": [
          {
            "campo": "nombre",
            "que": "Nombre del técnico.",
            "ejemplo": "Óscar Prato"
          },
          {
            "campo": "ciudad",
            "que": "La ciudad de la agenda. Vale el nombre o el id.",
            "acepta": [
              "nombre («Cali», «cali», «CALI»)",
              "id («ciu_mu603u2k1d8»)"
            ],
            "ejemplo": "Cali"
          }
        ],
        "opcionales": [
          "telefono — Celular del técnico.",
          "activo — Si aparece en la agenda. (por defecto: true)"
        ],
        "ejemplo": {
          "nombre": "Óscar Prato",
          "ciudad": "Cali",
          "telefono": "3104410217"
        },
        "devuelve": "El técnico creado, con su id.",
        "consejos": [
          "Dos técnicos no pueden llamarse igual dentro de la misma ciudad; en ciudades distintas sí."
        ]
      },
      {
        "llamada": "PATCH /api/tecnicos/:id",
        "operacion": "tecnicos.editar",
        "para": "Cambia el nombre, el teléfono, la ciudad o el estado de un técnico.",
        "obligatorios": [
          {
            "campo": "id",
            "que": "Id del técnico. Va en la ruta.",
            "ejemplo": "tec_mu604lgam21"
          }
        ],
        "opcionales": [
          "nombre — Nombre nuevo.",
          "ciudad — La ciudad de la agenda. Vale el nombre o el id.",
          "telefono — Celular.",
          "activo — Falso para que deje de aparecer en la agenda."
        ],
        "ejemplo": {
          "activo": false
        },
        "devuelve": "El técnico con los cambios aplicados.",
        "consejos": [
          "Cambiarlo de ciudad no mueve sus citas viejas: esas se quedan donde se hicieron."
        ]
      },
      {
        "llamada": "DELETE /api/tecnicos/:id",
        "operacion": "tecnicos.eliminar",
        "para": "Borra un técnico. Con citas en el historial hay que insistir con forzar.",
        "obligatorios": [
          {
            "campo": "id",
            "que": "Id del técnico. Va en la ruta.",
            "ejemplo": "tec_mu604lgam21"
          }
        ],
        "opcionales": [
          "forzar — Borra también todas sus citas, incluidas las ya hechas. (por defecto: false)"
        ],
        "ejemplo": {
          "forzar": true
        },
        "devuelve": "A quién se borró y cuántas citas se fueron con él.",
        "consejos": [
          "Si solo dejó de trabajar, usa PATCH con activo=false: desaparece de la agenda y el historial queda."
        ]
      },
      {
        "llamada": "GET /api/servicios",
        "operacion": "servicios.listar",
        "para": "El catálogo de servicios con su valor sugerido.",
        "obligatorios": [],
        "opcionales": [],
        "ejemplo": {},
        "devuelve": "Cada servicio con su nombre y su precio sugerido.",
        "consejos": [
          "Sirve para decirle al cliente cuánto cuesta antes de agendar."
        ]
      },
      {
        "llamada": "POST /api/servicios",
        "operacion": "servicios.crear",
        "para": "Agrega un servicio al catálogo.",
        "obligatorios": [
          {
            "campo": "nombre",
            "que": "Cómo se llama el servicio.",
            "ejemplo": "Colchón king"
          }
        ],
        "opcionales": [
          "valorSugerido — Precio que se pone solo al agendarlo.",
          "activo — Si se sugiere al agendar. (por defecto: true)"
        ],
        "ejemplo": {
          "nombre": "Colchón king",
          "valorSugerido": 110000
        },
        "devuelve": "El servicio creado.",
        "consejos": [
          "El catálogo no limita nada: en una cita el servicio se puede escribir libre."
        ]
      },
      {
        "llamada": "PATCH /api/servicios/:id",
        "operacion": "servicios.editar",
        "para": "Cambia el nombre, el precio sugerido o el estado de un servicio.",
        "obligatorios": [
          {
            "campo": "id",
            "que": "Id del servicio. Va en la ruta.",
            "ejemplo": "srv_007"
          }
        ],
        "opcionales": [
          "nombre — Nombre nuevo.",
          "valorSugerido — Precio sugerido.",
          "activo — Falso para dejar de sugerirlo."
        ],
        "ejemplo": {
          "valorSugerido": 125000
        },
        "devuelve": "El servicio con los cambios aplicados."
      },
      {
        "llamada": "DELETE /api/servicios/:id",
        "operacion": "servicios.eliminar",
        "para": "Saca un servicio del catálogo.",
        "obligatorios": [
          {
            "campo": "id",
            "que": "Id del servicio. Va en la ruta.",
            "ejemplo": "srv_007"
          }
        ],
        "opcionales": [],
        "ejemplo": {},
        "devuelve": "El nombre de lo que se borró.",
        "consejos": [
          "No toca ninguna cita: el servicio queda escrito en cada una."
        ]
      },
      {
        "llamada": "GET /api/agenda",
        "operacion": "agenda.dia",
        "para": "El cuadro de un día: cada técnico de la ciudad por cada uno de los tres turnos, con su cita o la casilla libre.",
        "obligatorios": [
          {
            "campo": "ciudad",
            "que": "La ciudad de la agenda. Vale el nombre o el id.",
            "acepta": [
              "nombre («Cali», «cali», «CALI»)",
              "id («ciu_mu603u2k1d8»)"
            ],
            "ejemplo": "Cali"
          }
        ],
        "opcionales": [
          "fecha — El día de la cita."
        ],
        "ejemplo": {
          "ciudad": "Cali",
          "fecha": "hoy"
        },
        "devuelve": "Los técnicos del día, las nueve o más casillas y los totales (citas, libres, dinero).",
        "consejos": [
          "Sin «fecha» se entiende hoy.",
          "Para saber solo dónde hay cupo, /disponibilidad devuelve mucho menos y es más fácil de leer."
        ]
      },
      {
        "llamada": "GET /api/disponibilidad",
        "operacion": "agenda.disponibilidad",
        "para": "Solo las casillas libres. Es la consulta para responder «¿para cuándo tienen?».",
        "obligatorios": [
          {
            "campo": "ciudad",
            "que": "La ciudad de la agenda. Vale el nombre o el id.",
            "acepta": [
              "nombre («Cali», «cali», «CALI»)",
              "id («ciu_mu603u2k1d8»)"
            ],
            "ejemplo": "Cali"
          }
        ],
        "opcionales": [
          "desde — Desde qué día se busca. (por defecto: hoy)",
          "dias — Cuántos días mirar hacia adelante (máximo 60). (por defecto: 7)",
          "horario — Para mirar un solo turno."
        ],
        "ejemplo": {
          "ciudad": "Cali",
          "desde": "hoy",
          "dias": 5
        },
        "devuelve": "Una lista de huecos: fecha, turno y qué técnicos están libres en cada uno.",
        "consejos": [
          "Pregunta esto antes de agendar y ofrécele al cliente dos o tres opciones concretas.",
          "Si vuelve vacío, la ciudad no tiene técnicos activos o está todo lleno: amplía «dias»."
        ]
      },
      {
        "llamada": "GET /api/tapetes/tablero",
        "operacion": "tapetes.tablero",
        "para": "El taller de un vistazo: qué hay por recoger, qué se está lavando, qué está listo y qué se entregó.",
        "obligatorios": [],
        "opcionales": [
          "ciudad — La ciudad de la agenda. Vale el nombre o el id.",
          "q — Filtra por cliente, teléfono, dirección o barrio."
        ],
        "ejemplo": {
          "ciudad": "Cali"
        },
        "devuelve": "Las cuatro columnas con sus tapetes, más un resumen (cuántos en taller, cuántos atrasados, cuánto está por cobrar).",
        "consejos": [
          "Los tapetes no tienen técnico ni turno: se lavan en la empresa, así que no hay casillas que chocar.",
          "Los atrasados salen primero en cada columna: una recogida que ya pasó de fecha o un tapete listo sin entregar hace días."
        ]
      },
      {
        "llamada": "GET /api/tapetes",
        "operacion": "tapetes.listar",
        "para": "Busca tapetes por cliente, teléfono, dirección, barrio o lo que se anotó.",
        "obligatorios": [],
        "opcionales": [
          "q — Texto libre.",
          "ciudad — La ciudad de la agenda. Vale el nombre o el id.",
          "estado — Para ver solo los de un paso. (por defecto: por_recoger)",
          "desde — Recogidos desde esta fecha.",
          "hasta — Recogidos hasta esta fecha."
        ],
        "ejemplo": {
          "estado": "listo"
        },
        "devuelve": "Los tapetes con su ciudad, cuántos días llevan parados y si están atrasados.",
        "consejos": [
          "Para saber a quién llamar hoy: estado=listo son los que ya se pueden devolver."
        ]
      },
      {
        "llamada": "POST /api/tapetes",
        "operacion": "tapetes.crear",
        "para": "Anota unos tapetes que se van a recoger (o que ya se recogieron).",
        "obligatorios": [
          {
            "campo": "ciudad",
            "que": "La ciudad de la agenda. Vale el nombre o el id.",
            "acepta": [
              "nombre («Cali», «cali», «CALI»)",
              "id («ciu_mu603u2k1d8»)"
            ],
            "ejemplo": "Cali"
          }
        ],
        "opcionales": [
          "cliente — De quién son los tapetes.",
          "telefono — Celular, para avisar cuando estén listos.",
          "direccion — Dónde se recogen y se devuelven.",
          "barrio — Barrio o sector.",
          "descripcion — Qué se recogió, con sus medidas. Texto libre: «2 de sala, uno de 3×4».",
          "cantidad — Cuántos tapetes son. (por defecto: 1)",
          "valor — Lo que se cobra por el trabajo completo, en pesos.",
          "nota — Manchas, cuidados, con quién dejarlo.",
          "fechaRecoge — Cuándo se recoge en casa del cliente. (por defecto: hoy)",
          "fechaEntrega — Cuándo se promete devolverlo. Al entregarlo se pone sola la del día.",
          "estado — En qué paso va: se recoge, se lava en la empresa y se devuelve. (por defecto: por_recoger)"
        ],
        "ejemplo": {
          "ciudad": "Cali",
          "cliente": "Martha Ruiz",
          "telefono": "3104410217",
          "direccion": "Calle 4 #60-28",
          "barrio": "Pampalinda",
          "descripcion": "2 tapetes de sala, uno de 3×4",
          "cantidad": 2,
          "valor": 120000,
          "fechaRecoge": "mañana"
        },
        "devuelve": "El tapete creado, con su id.",
        "consejos": [
          "Si ya lo tienes en el taller, manda estado=en_lavado y la fecha de recogida se marca sola.",
          "La ciudad sirve para saber de dónde salió y a dónde hay que devolverlo."
        ]
      },
      {
        "llamada": "POST /api/tapetes/:id/avanzar",
        "operacion": "tapetes.avanzar",
        "para": "Pasa el tapete al siguiente paso: por recoger → en lavado → listo → entregado.",
        "obligatorios": [
          {
            "campo": "id",
            "que": "Id del tapete. Va en la ruta.",
            "ejemplo": "tap_mu60f2k1d8"
          }
        ],
        "opcionales": [],
        "ejemplo": {},
        "devuelve": "El tapete en su paso nuevo, con la marca de cuándo cambió.",
        "consejos": [
          "Es el gesto del día a día: no hay que saberse el nombre del paso siguiente.",
          "Al entregarlo, la fecha de entrega se pone sola y el dinero cuenta como cobrado."
        ]
      },
      {
        "llamada": "PATCH /api/tapetes/:id",
        "operacion": "tapetes.editar",
        "para": "Corrige cualquier dato de unos tapetes, incluido el paso en que van.",
        "obligatorios": [
          {
            "campo": "id",
            "que": "Id del tapete. Va en la ruta.",
            "ejemplo": "tap_mu60f2k1d8"
          }
        ],
        "opcionales": [
          "ciudad — La ciudad de la agenda. Vale el nombre o el id.",
          "cliente — De quién son los tapetes.",
          "telefono — Celular, para avisar cuando estén listos.",
          "direccion — Dónde se recogen y se devuelven.",
          "barrio — Barrio o sector.",
          "descripcion — Qué se recogió, con sus medidas. Texto libre: «2 de sala, uno de 3×4».",
          "cantidad — Cuántos tapetes son. (por defecto: 1)",
          "valor — Lo que se cobra por el trabajo completo, en pesos.",
          "nota — Manchas, cuidados, con quién dejarlo.",
          "fechaRecoge — Cuándo se recoge en casa del cliente. (por defecto: hoy)",
          "fechaEntrega — Cuándo se promete devolverlo. Al entregarlo se pone sola la del día.",
          "estado — En qué paso va: se recoge, se lava en la empresa y se devuelve. (por defecto: por_recoger)"
        ],
        "ejemplo": {
          "valor": 150000
        },
        "devuelve": "El tapete con los cambios aplicados.",
        "consejos": [
          "Manda solo lo que cambia. Para devolverlo a un paso anterior, usa «estado»."
        ]
      },
      {
        "llamada": "DELETE /api/tapetes/:id",
        "operacion": "tapetes.eliminar",
        "para": "Borra un registro de tapetes.",
        "obligatorios": [
          {
            "campo": "id",
            "que": "Id del tapete. Va en la ruta.",
            "ejemplo": "tap_mu60f2k1d8"
          }
        ],
        "opcionales": [],
        "ejemplo": {},
        "devuelve": "El id de lo que se borró.",
        "consejos": [
          "Si el cliente se arrepintió, mejor PATCH con estado=cancelado: queda el rastro."
        ]
      },
      {
        "llamada": "GET /api/ingresos",
        "operacion": "ingresos.resumen",
        "para": "El dinero de un periodo: cuánto entró, cuánto falta por cobrar, y el reparto por técnico y por ciudad.",
        "obligatorios": [],
        "opcionales": [
          "periodo — Atajo del periodo. Sin él ni fechas, se entiende el mes en curso. (por defecto: mes)",
          "desde — Primer día, si prefieres dar fechas exactas.",
          "hasta — Último día. Sin él, es el mismo que «desde».",
          "ciudad — La ciudad de la agenda. Vale el nombre o el id."
        ],
        "ejemplo": {
          "periodo": "mes"
        },
        "devuelve": "Totales (cobrado, por cobrar, ticket promedio, promedio diario), el reparto por técnico y por ciudad, y el día a día.",
        "consejos": [
          "Lo «cobrado» son las citas completadas; lo «por cobrar», las pendientes y confirmadas. No los sumes como si fueran lo mismo.",
          "Para saber cuánta plata tiene cada técnico encima, mira «porTecnico»: es lo que recogió en el periodo."
        ]
      },
      {
        "llamada": "GET /api/estadisticas",
        "operacion": "estadisticas",
        "para": "Los números para tomar decisiones: qué ciudad pide más, qué técnico rinde más, qué servicio se pide más, ocupación, barrios y clientes que vuelven.",
        "obligatorios": [],
        "opcionales": [
          "periodo — Atajo del periodo. Sin él ni fechas, se entiende el mes en curso. (por defecto: mes)",
          "desde — Primer día, si prefieres dar fechas exactas.",
          "hasta — Último día.",
          "ciudad — La ciudad de la agenda. Vale el nombre o el id."
        ],
        "ejemplo": {
          "periodo": "ultimos90"
        },
        "devuelve": "Ranking de ciudades, técnicos, servicios y barrios; ocupación por turno; días de la semana; evolución por mes; clientes que repiten; y «hallazgos», que son las conclusiones ya escritas.",
        "consejos": [
          "Si te preguntan «¿cómo va el negocio?», lee «hallazgos»: ya trae las conclusiones en frases.",
          "La ocupación es sobre todas las casillas del periodo (3 turnos × técnico × día), domingos incluidos."
        ]
      },
      {
        "llamada": "GET /api/citas",
        "operacion": "citas.buscar",
        "para": "Busca citas en todo el historial.",
        "obligatorios": [],
        "opcionales": [
          "q — Texto libre: entra por nombre, teléfono, dirección, barrio, servicio y nota a la vez.",
          "ciudad — La ciudad de la agenda. Vale el nombre o el id.",
          "tecnico — Para ver solo las citas de un técnico.",
          "estado — En qué va la cita. «cancelada» libera la casilla pero conserva el historial. (por defecto: pendiente)",
          "horario — Solo las de un turno.",
          "desde — Fecha mínima.",
          "hasta — Fecha máxima.",
          "limite — Cuántas devolver (máximo 500). (por defecto: 50)",
          "desplazamiento — Cuántas saltar, para pedir la página siguiente. (por defecto: 0)"
        ],
        "ejemplo": {
          "q": "3104410217"
        },
        "devuelve": "El total, y las citas ordenadas de la más reciente a la más vieja.",
        "consejos": [
          "Para encontrar a alguien que llama, busca por su teléfono en «q»: da igual cómo esté escrito en la ficha.",
          "Si no encuentras nada, quita filtros antes de decir que no existe."
        ]
      },
      {
        "llamada": "GET /api/citas/:id",
        "operacion": "citas.obtener",
        "para": "Una cita concreta, con la ciudad y el técnico resueltos por nombre.",
        "obligatorios": [
          {
            "campo": "id",
            "que": "Id de la cita. Va en la ruta.",
            "ejemplo": "cit_mu604lghfv5"
          }
        ],
        "opcionales": [],
        "ejemplo": {},
        "devuelve": "La cita completa."
      },
      {
        "llamada": "POST /api/citas",
        "operacion": "citas.crear",
        "para": "Agenda una cita en una casilla libre.",
        "obligatorios": [
          {
            "campo": "ciudad",
            "que": "La ciudad de la agenda. Vale el nombre o el id.",
            "acepta": [
              "nombre («Cali», «cali», «CALI»)",
              "id («ciu_mu603u2k1d8»)"
            ],
            "ejemplo": "Cali"
          },
          {
            "campo": "fecha",
            "que": "El día de la cita.",
            "acepta": [
              "AAAA-MM-DD (2026-09-18)",
              "hoy",
              "mañana",
              "pasado mañana",
              "ayer",
              "en N días («en 3 días»)",
              "DD/MM/AAAA (18/09/2026)"
            ],
            "ejemplo": "mañana"
          },
          {
            "campo": "horario",
            "que": "El turno. Solo hay tres en todo el día.",
            "acepta": [
              "09:00 (9:00 a.m.)",
              "12:00 (12:00 p.m.)",
              "15:00 (3:00 p.m.)",
              "9am / 12pm / 3pm",
              "mañana / mediodía / tarde"
            ],
            "ejemplo": "9am"
          }
        ],
        "opcionales": [
          "tecnico — Quién la atiende. Si no lo dices, se asigna el primero libre de esa ciudad y turno.",
          "servicio — Qué se va a lavar. Texto libre; si coincide con el catálogo, el valor se pone solo.",
          "cliente — Nombre de quien pide el servicio.",
          "telefono — Celular del cliente. Se guarda en dígitos; da igual cómo venga escrito.",
          "direccion — Dónde es el servicio.",
          "barrio — Barrio o sector. Se usa mucho al buscar después.",
          "valor — Cuánto se cobra, en pesos y sin decimales.",
          "nota — Lo que haya que recordar: torre, apartamento, a quién llamar.",
          "localidad — La portería o el conjunto ya autorizaron la entrada del técnico. (por defecto: false)",
          "estado — En qué va la cita. «cancelada» libera la casilla pero conserva el historial. (por defecto: pendiente)"
        ],
        "ejemplo": {
          "ciudad": "Cali",
          "fecha": "mañana",
          "horario": "9am",
          "servicio": "Sala 3 puestos",
          "cliente": "Freisa Bedoya",
          "telefono": "3104410217",
          "direccion": "Calle 4 #60-28",
          "barrio": "Pampalinda",
          "valor": 119000
        },
        "devuelve": "La cita creada, con el técnico que quedó asignado.",
        "consejos": [
          "No hace falta mandar «tecnico»: sin él se asigna el primero libre, que es lo que hace quien agenda por teléfono.",
          "Si el servicio está en el catálogo y no mandas «valor», se usa el precio sugerido.",
          "Si la casilla está ocupada, el error trae «sugerencias» con los huecos más cercanos: ofrécele uno al cliente."
        ]
      },
      {
        "llamada": "PATCH /api/citas/:id",
        "operacion": "citas.editar",
        "para": "Cambia cualquier dato de una cita, incluida la casilla.",
        "obligatorios": [
          {
            "campo": "id",
            "que": "Id de la cita. Va en la ruta.",
            "ejemplo": "cit_mu604lghfv5"
          }
        ],
        "opcionales": [
          "ciudad — La ciudad de la agenda. Vale el nombre o el id.",
          "fecha — El día de la cita.",
          "horario — El turno. Solo hay tres en todo el día.",
          "tecnico — Para pasársela a otro técnico.",
          "servicio — Qué se va a lavar. Texto libre; si coincide con el catálogo, el valor se pone solo.",
          "cliente — Nombre de quien pide el servicio.",
          "telefono — Celular del cliente. Se guarda en dígitos; da igual cómo venga escrito.",
          "direccion — Dónde es el servicio.",
          "barrio — Barrio o sector. Se usa mucho al buscar después.",
          "valor — Cuánto se cobra, en pesos y sin decimales.",
          "nota — Lo que haya que recordar: torre, apartamento, a quién llamar.",
          "localidad — La portería o el conjunto ya autorizaron la entrada del técnico. (por defecto: false)",
          "estado — En qué va la cita. «cancelada» libera la casilla pero conserva el historial. (por defecto: pendiente)"
        ],
        "ejemplo": {
          "estado": "confirmada"
        },
        "devuelve": "La cita con los cambios aplicados.",
        "consejos": [
          "Manda solo lo que cambia: lo que no envíes se queda como estaba.",
          "Para cancelar, usa esto con estado=cancelada. No borres la cita: así queda el rastro de que existió."
        ]
      },
      {
        "llamada": "POST /api/citas/:id/mover",
        "operacion": "citas.mover",
        "para": "Cambia el día, el turno o el técnico sin tocar los datos del cliente.",
        "obligatorios": [
          {
            "campo": "id",
            "que": "Id de la cita. Va en la ruta.",
            "ejemplo": "cit_mu604lghfv5"
          }
        ],
        "opcionales": [
          "fecha — El día de la cita.",
          "horario — El turno. Solo hay tres en todo el día.",
          "tecnico — Para pasársela a otro técnico."
        ],
        "ejemplo": {
          "fecha": "en 2 dias",
          "horario": "3 pm"
        },
        "devuelve": "La cita en su casilla nueva.",
        "consejos": [
          "Es lo que se pide por teléfono: «pásamela al jueves». Más seguro que PATCH porque no puede borrar la ficha sin querer."
        ]
      },
      {
        "llamada": "DELETE /api/citas/:id",
        "operacion": "citas.eliminar",
        "para": "Borra una cita del historial para siempre.",
        "obligatorios": [
          {
            "campo": "id",
            "que": "Id de la cita. Va en la ruta.",
            "ejemplo": "cit_mu604lghfv5"
          }
        ],
        "opcionales": [],
        "ejemplo": {},
        "devuelve": "El id de lo que se borró.",
        "consejos": [
          "Si el cliente canceló, no uses esto: PATCH con estado=cancelada libera la casilla y deja constancia."
        ]
      }
    ],
    "ejemplosCurl": [
      "curl -H \"Authorization: Bearer TOKEN\" \"http://127.0.0.1:8787/api/disponibilidad?ciudad=Cali&desde=hoy&dias=5\"",
      "curl -X POST -H \"Authorization: Bearer TOKEN\" -H \"Content-Type: application/json\" \\\n  -d '{\"ciudad\":\"Cali\",\"fecha\":\"mañana\",\"horario\":\"9am\",\"servicio\":\"Sala 3 puestos\",\"cliente\":\"Freisa Bedoya\",\"telefono\":\"3104410217\",\"direccion\":\"Calle 4 #60-28\",\"barrio\":\"Pampalinda\",\"valor\":119000}' \\\n  http://127.0.0.1:8787/api/citas",
      "curl -H \"Authorization: Bearer TOKEN\" \"http://127.0.0.1:8787/api/ayuda?de=citas.crear\""
    ]
  }
}